2
$\begingroup$

I'm new Blender and I'm really excited about the Grease Pencil feature but I'm finding it difficult to achieve a function I use in other 2D animation programs, namely rotating/rolling the camera view while in a drawing mode.

In Toon Boom's products you have the ability to rotate the camera view of the canvas to make it easier to draw at an angle without transforming the drawn elements. To do this you simply hold Ctrl+Alt (which brings up a overlay of an animation disk) and then Left-Drag to set the rotation angle. To reset the view you press Shift+Z.

Demonstration of Toon Boom Storyboard rotating canvass

To rotate the camera view in Blender I find I must escape out of Draw Mode to Object Mode, select the camera, rotate/roll the view, and re-enter the Draw Mode.

In searching for a better solution I found this which links a video that supports a similar feature but I feel it's inefficient.

The link goes on to explain that pressing R will achieve what I want but I don't seem to be able to get it to work as described.

I believe I should be able to write a script to add a shortcut. I spent some time on it but not being familiar with Blender scripting I find it difficult to even know how to approach this problem.

I found a simple framework here for adding a shortcut that seems to work but the business logic escapes me. Essentially my approach has been to assign a keyboard shortcut to Ctrl+Alt+Left-Drag while in Draw Mode to go directly into rotating/rolling the camera, and then upon release go back to Draw Mode on the object I was just drawing in.

Does anyone have similar script or helpful direction?

$\endgroup$
  • $\begingroup$ They are incremental, but try $ and ^. $\endgroup$ – kitsu.eb Apr 7 at 16:34
  • $\begingroup$ $ and ^ don't seem to work for me $\endgroup$ – bigLarry Apr 9 at 12:46
  • $\begingroup$ I was watching an old YouTube stream by HeavyPoly last night, and he seemed to be able to free-rotate around the view axis. I don't know how, but he does do some scripting, so it might be custom... $\endgroup$ – kitsu.eb Apr 10 at 15:11
  • $\begingroup$ I just found under View > Navigation > Roll Left (shift numpad 4) and similar for right. I searched but there is no binding under Keymap for mouse rolling. Searching for roll I found this: blender.stackexchange.com/q/15049/454 $\endgroup$ – kitsu.eb Apr 10 at 17:43
  • $\begingroup$ Saw that article and tried that too but it doesn't seem to work for me. $\endgroup$ – bigLarry Apr 11 at 0:30
1
$\begingroup$

So I wrote this script to perform the task of rotating the camera while in the Grease Pencil Draw mode.

Basically Ctrl+Alt+Middle-Click in a region of the 3D viewport to rotate the camera.

enter image description here

  • Left region will rotate counter-clockwise.
  • Right region will rotate clock-wise.
  • Middle region will reset the camera roll to 0.

It works but useful feedback and slicker solutions are welcomed.

import bpy

bl_info = {
    "name": "GPC Rotate",
    "blender": (2, 80, 0),
    "category": "Object",
}
addon_keymaps = []
rota = 0.2
cent = 0.20

class GPCRot(bpy.types.Operator):
    """Grease Pencil camera rotate"""
    bl_idname = "object.gpcr"
    bl_label = "GPC Rotate"
    bl_options = {'REGISTER', 'UNDO'}

    def execute(self, context):
        return {'FINISHED'}

    def invoke(self, context, event):
        if str(bpy.context.active_object.mode) == "PAINT_GPENCIL":
            cam = bpy.context.scene.camera
            ca,cb,cc = cam.rotation_euler
            for area in bpy.context.screen.areas:
                if area.type == 'VIEW_3D':
                    cx = event.mouse_region_x - (area.width/2)
                    if abs(cx) < cent*area.width:
                        cam.rotation_euler = (ca, 0, cc)
                    elif cx > 0:
                        cam.rotation_euler = (ca, cb-rota, cc)
                    else:
                        cam.rotation_euler = (ca, cb+rota, cc)
                    print(cx)
        return self.execute(context)

def register():
    bpy.utils.register_class(GPCRot)
    wm = bpy.context.window_manager
    km = wm.keyconfigs.addon.keymaps.new(name='Grease Pencil', space_type='EMPTY')
    kmi = km.keymap_items.new(GPCRot.bl_idname, 'MIDDLEMOUSE', 'PRESS', ctrl=True, shift=False, alt=True)
    addon_keymaps.append(km)

def unregister():
    bpy.utils.unregister_class(GPCRot)
    wm = bpy.context.window_manager
    for km in addon_keymaps:
        wm.keyconfigs.addon.keymaps.remove(km)
    del addon_keymaps[:]

if __name__ == "__main__":
    register()
$\endgroup$
  • $\begingroup$ I like the code. Perfect work! $\endgroup$ – 22melloja Apr 10 at 21:52
0
$\begingroup$

I hope you don't mind. I studied your code and the code of other users and I believe I was able to implement your initial idea. I can't tell if it's the best solution because it's the first time in blender. I usually program for Maya and Nuke. So just create the shortcut (Ctrl + Alt + Left click) for object.rotate canvas.

import bpy
import math
import mathutils
from bpy_extras.view3d_utils import location_3d_to_region_2d

"""
    Script base. Thanks to bigLarry
    ######################################################################################################################
    https://blender.stackexchange.com/questions/136183/rotating-camera-view-in-grease-pencil-draw-mode-in-blender-2-8
    ######################################################################################################################


"""
bl_info = {
    "name": "Rotate Canvas",
    "blender": (2, 80, 0),
    "category": "Object",
}
class RotateCanvas_(bpy.types.Operator):
    bl_idname = 'object.rotate_canvas'
    bl_label = 'Rotate Canvas'

    def __init__(self):
        print ('\n\nStart - Rotate Canvas')

    def __del__(self):
        print ('End - Rotate Canvas\n\n')

    def _get_center_view(self, area, cam):
        '''
        https://blender.stackexchange.com/questions/6377/coordinates-of-corners-of-camera-view-border
        Thanks to ideasman42
        '''
        region_3d = area.spaces[0].region_3d
        for region in area.regions:
            if region.type == 'WINDOW':
                frame = cam.data.view_frame()
                frame = [cam.matrix_world @ v for v in frame]
                frame_px = [location_3d_to_region_2d(region, region_3d, v) for v in frame]

                center_x = frame_px[2].x + (frame_px[0].x - frame_px[2].x)/2
                center_y = frame_px[1].y + (frame_px[0].y - frame_px[1].y)/2
                return mathutils.Vector((center_x, center_y))
        return None

    def _get_camera_rotation(self):
        """
            Get euler rotation and return quaternion rotation.

        :return:
            quaternion - cam_rotation_quat
        """
        # Get camera rotation.
        cam_rotation_euler = self.cam.rotation_euler

        # Convert euler to quaternion
        cam_rotation_quat = cam_rotation_euler.to_quaternion()

        return cam_rotation_quat

    def _get_camera_aim_vector(self):
        """
            Return camera aim vector.

        :return:
            Vector - aim
        """
        aim = self.cam.matrix_world.to_quaternion() @ mathutils.Vector((0.0, 0.0, -1.0))

        return aim

    def _apply_rotation(self, angle):
        """
            Apply rotation based on aim vector.

        :return:
            Vector - cam_rot_euler
        """
        # Create a new quaternion using self.cam_aim_vector and angle
        new_quaternion = mathutils.Quaternion(self.cam_aim_vector, angle * -1)

        # Duplicate cam quaternion.
        cam_rot_quat_duplicate = self.cam_rot_quat.copy()

        # Use new quaternion to rotate duplicated quaternion.
        cam_rot_quat_duplicate.rotate(new_quaternion)

        # Convert to euler rotation
        cam_rot_euler = cam_rot_quat_duplicate.to_euler()

        return cam_rot_euler

    def execute(self, context):
        # Calculates the angle between initial and current vectors
        angle = self.vector_initial.angle_signed(self.vector_current)

        # Calculate a camera new angle 
        new_cam_rot = self._apply_rotation(angle)

        # Apply a new angle
        self.cam.rotation_euler = new_cam_rot

        #print (math.degrees(angle), self.vector_initial, self.vector_current)

        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == 'MOUSEMOVE' or event.type == 'INBETWEEN_MOUSEMOVE':
            # Get current mouse coordination (region)
            self.pos_current = mathutils.Vector((event.mouse_region_x, event.mouse_region_y))

            # Get current vector
            self.vector_current = (self.pos_current - self.center).normalized()
            self.execute(context)

        else:
            return {'FINISHED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        #print ('Invoke - RotateCanvas')
        # Get camera from scene
        self.cam = bpy.context.scene.camera
        # Get camera initial rotation
        self.cam_rot_quat = self._get_camera_rotation()
        # Recupera o aim vector da camera
        self.cam_aim_vector = self._get_camera_aim_vector()

        # Get the center of the camera on canvas.
        for area in bpy.context.screen.areas:
            if area.type == 'VIEW_3D':
                self.center = self._get_center_view(area, self.cam)

        # Get current mouse coordination
        self.pos_current = mathutils.Vector((event.mouse_region_x, event.mouse_region_y))

        # Calculate inital vector
        self.vector_initial = self.pos_current - self.center
        self.vector_initial.normalize()


        # Initializes the current vector with the same initial vector.
        self.vector_current = self.vector_initial.copy()

        self.execute(context)

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

def register():
    bpy.utils.register_class(RotateCanvas_)


def unregister():
    bpy.utils.unregister_class(RotateCanvas_)

if __name__ == "__main__":
    register()
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.