3
$\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$
6
  • $\begingroup$ They are incremental, but try $ and ^. $\endgroup$
    – kitsu.eb
    Commented Apr 7, 2019 at 16:34
  • $\begingroup$ $ and ^ don't seem to work for me $\endgroup$
    – bigLarry
    Commented Apr 9, 2019 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
    Commented Apr 10, 2019 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
    Commented Apr 10, 2019 at 17:43
  • $\begingroup$ Saw that article and tried that too but it doesn't seem to work for me. $\endgroup$
    – bigLarry
    Commented Apr 11, 2019 at 0:30

3 Answers 3

4
$\begingroup$

As others have already pointed out that Shift + Numpad 4 and Shift + Numpad 6 rotate the view by increments. This is ok and already works on the default set up.

For me, the better solution was to go to Edit > Preferences Keymap under Grease Pencil > Grease Pencil(global) press Add New. From here set your preferred key binding (I chose Middle Mouse Button ) then click the little "expand" arrow, add any modifiers (I chose Shift + Alt) and type `view3d.view_roll" into the command box.

And that's it, this allows arbitrary rotation while holding Shift + Alt + Middle Mouse Button . To reset press Shift + Numpad 1.

Add the following to allow arbitrary rotation of the canvas in Grease Pencil

$\endgroup$
2
  • $\begingroup$ This is great, but I wasn't able to get it to work. Two questions: 1) Is it really as you show in your pic, and we needn't specify a value for Roll, or an Angle under Source? I do what you show, but nothing happens when I press modifier keys and move the mouse. So I wonder if more is needed before mouse movement will register. 2) Did you manually set up the condition that resets the view using Shift + Numpad 1? Or is this a built in feature of the view3d roll function? You don't mention needing to do anything to create this functionality, so I assume its expected to work automatically. $\endgroup$
    – R-800
    Commented Jun 25, 2020 at 7:21
  • 1
    $\begingroup$ If you open a new 2D animation it will start you in "camera perspective" (visible in the upper left). Press numpad 0 to switch to "user orthographic" for these commands to work as expected. You may need to hide the camera in the scene collection. Shift-numpad 1 is indeed a default command. $\endgroup$ Commented Jun 26, 2020 at 9:08
2
$\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$
1
  • $\begingroup$ I like the code. Perfect work! $\endgroup$
    – 22melloja
    Commented Apr 10, 2019 at 21:52
1
$\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$
1
  • $\begingroup$ Your solution is the best to me, but it does not work once the camera is parented to an object that has a rotation, I'm trying to adapt your solution but I'm struggling with matrix, vectors rotation and such (linear algebra is not my cup of tea...) $\endgroup$
    – Pullup
    Commented Jul 4, 2020 at 22:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .