diff options
author | Pullusb <bernou.samuel@gmail.com> | 2021-01-31 23:44:03 +0300 |
---|---|---|
committer | Pullusb <bernou.samuel@gmail.com> | 2021-01-31 23:44:03 +0300 |
commit | 7bc0fa6bf2d8fb564759a4b2c776eda098f61bd9 (patch) | |
tree | ab248ed28b48e6825ee53a8baa6b33b9520a377a /greasepencil_tools | |
parent | 9d30d2bccb154f2c1a4b665bab7d6c02ff5c51b1 (diff) |
GPencil Tools: Canvas rotation improvements
- Added operators to store/restore camera rotation (handy to quickly get back to a prefered angle)
- Use a modifier key to step the rotation by a user-chosen angle
- Addon preferences UI cleanup
Diffstat (limited to 'greasepencil_tools')
-rw-r--r-- | greasepencil_tools/__init__.py | 2 | ||||
-rw-r--r-- | greasepencil_tools/prefs.py | 185 | ||||
-rw-r--r-- | greasepencil_tools/rotate_canvas.py | 93 | ||||
-rw-r--r-- | greasepencil_tools/ui_panels.py | 3 |
4 files changed, 193 insertions, 90 deletions
diff --git a/greasepencil_tools/__init__.py b/greasepencil_tools/__init__.py index 9ac882ba..adcc7aa7 100644 --- a/greasepencil_tools/__init__.py +++ b/greasepencil_tools/__init__.py @@ -21,7 +21,7 @@ bl_info = { "name": "Grease Pencil Tools", "description": "Extra tools for Grease Pencil", "author": "Samuel Bernou, Antonio Vazquez, Daniel Martinez Lara, Matias Mendiola", -"version": (1, 2, 2), +"version": (1, 3, 0), "blender": (2, 91, 0), "location": "Sidebar > Grease Pencil > Grease Pencil Tools", "warning": "", diff --git a/greasepencil_tools/prefs.py b/greasepencil_tools/prefs.py index fff0f18c..533f5b99 100644 --- a/greasepencil_tools/prefs.py +++ b/greasepencil_tools/prefs.py @@ -23,6 +23,7 @@ from bpy.props import ( EnumProperty, StringProperty, PointerProperty, + FloatProperty, # IntProperty, ) @@ -62,13 +63,6 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences): default="Grease Pencil", update=update_panel) - pref_tabs : EnumProperty( - items=(('PREF', "Preferences", "Preferences properties of GP"), - ('TUTO', "Tutorial", "How to use the tool"), - # ('KEYMAP', "Keymap", "customise the default keymap"), - ), - default='PREF') - # --- props use_clic_drag : BoolProperty( name='Use click drag directly on points', @@ -131,95 +125,124 @@ class GreasePencilAddonPrefs(bpy.types.AddonPreferences): default = True, update=auto_rebind) + rc_angle_step: FloatProperty( + name="Angle Steps", + description="Step the rotation using this angle when using rotate canvas step modifier", + default=0.0872664600610733, # 5 + min=0.01745329238474369, # 1 + max=3.1415927410125732, # # 180 + soft_min=0.01745329238474369, # 1 + soft_max=1.5707963705062866, # 90 + step=10, precision=1, subtype='ANGLE', unit='ROTATION') + def draw(self, context): prefs = get_addon_prefs() layout = self.layout # layout.use_property_split = True row= layout.row(align=True) - row.prop(self, "pref_tabs", expand=True) - if self.pref_tabs == 'PREF': + ## TAB CATEGORY + box = layout.box() + row = box.row(align=True) + row.label(text="Panel Category:") + row.prop(self, "category", text="") + + ## BOX DEFORM + box = layout.box() + row = box.row(align=True) + row.label(text='Box Deform:') + row.operator("wm.call_menu", text="", icon='QUESTION').name = "GPT_MT_box_deform_doc" + box.prop(self, "use_clic_drag") + # box.separator() + box.prop(self, "default_deform_type") + box.label(text="Deformer type can be changed during modal with 'M' key, this is for default behavior", icon='INFO') + + box.prop(self, "auto_swap_deform_type") + box.label(text="Once 'M' is hit, auto swap is desactivated to stay in your chosen mode", icon='INFO') + + ## ROTATE CANVAS + box = layout.box() + box.label(text='Rotate canvas:') + + box.prop(self, "canvas_use_shortcut", text='Bind Shortcuts') + + if self.canvas_use_shortcut: + + row = box.row() + row.label(text="(Auto rebind when changing shortcut)")#icon="" + # row.operator("prefs.rebind_shortcut", text='Bind/Rebind shortcuts', icon='FILE_REFRESH')#EVENT_SPACEKEY + row = box.row(align = True) + row.prop(self, "use_ctrl", text='Ctrl')#, expand=True + row.prop(self, "use_alt", text='Alt')#, expand=True + row.prop(self, "use_shift", text='Shift')#, expand=True + row.prop(self, "mouse_click",text='')#expand=True + + if not self.use_ctrl and not self.use_alt and not self.use_shift: + box.label(text="Choose at least one modifier to combine with click (default: Ctrl+Alt)", icon="ERROR")# INFO + + if not all((self.use_ctrl, self.use_alt, self.use_shift)): + row = box.row(align = True) + snap_key_list = [] + if not self.use_ctrl: + snap_key_list.append('Ctrl') + if not self.use_shift: + snap_key_list.append('Shift') + if not self.use_alt: + snap_key_list.append('Alt') - ## TAB CATEGORY - box = layout.box() - row = box.row(align=True) - row.label(text="Panel Category:") - row.prop(self, "category", text="") + row.label(text=f"Step rotation with: {' or '.join(snap_key_list)}", icon='DRIVER_ROTATIONAL_DIFFERENCE') + row.prop(self, "rc_angle_step", text='Angle Steps') - ## BOX DEFORM - box = layout.box() - box.label(text='Box Deform:') - box.prop(self, "use_clic_drag") - # box.separator() - box.prop(self, "default_deform_type") - box.label(text="Deformer type can be changed during modal with 'M' key, this is for default behavior", icon='INFO') - box.prop(self, "auto_swap_deform_type") - box.label(text="Once 'M' is hit, auto swap is desactivated to stay in your chosen mode", icon='INFO') + else: + box.label(text="No hotkey has been set automatically. Following operators needs to be set manually:", icon="ERROR") + box.label(text="view3d.rotate_canvas") + box.prop(self, 'canvas_use_hud') - ## ROTATE CANVAS - box = layout.box() - box.label(text='Rotate canvas:') + ## SCRUB TIMELINE + box = layout.box() + draw_ts_pref(prefs.ts, box) - box.prop(self, "canvas_use_shortcut", text='Bind Shortcuts') - if self.canvas_use_shortcut: - - row = box.row() - row.label(text="(Auto rebind when changing shortcut)")#icon="" - # row.operator("prefs.rebind_shortcut", text='Bind/Rebind shortcuts', icon='FILE_REFRESH')#EVENT_SPACEKEY - row = box.row(align = True) - row.prop(self, "use_ctrl", text='Ctrl')#, expand=True - row.prop(self, "use_alt", text='Alt')#, expand=True - row.prop(self, "use_shift", text='Shift')#, expand=True - row.prop(self, "mouse_click",text='')#expand=True - - if not self.use_ctrl and not self.use_alt and not self.use_shift: - box.label(text="Choose at least one modifier to combine with click (default: Ctrl+Alt)", icon="ERROR")# INFO - - else: - box.label(text="No hotkey has been set automatically. Following operators needs to be set manually:", icon="ERROR") - box.label(text="view3d.rotate_canvas") - box.prop(self, 'canvas_use_hud') - - ## SCRUB TIMELINE - box = layout.box() - draw_ts_pref(prefs.ts, box) - - if self.pref_tabs == 'TUTO': - - #**Behavior from context mode** - col = layout.column() - col.label(text='Box Deform Tool') - col.label(text="Usage:", icon='MOD_LATTICE') - col.label(text="Use the shortcut 'Ctrl+T' in available modes (listed below)") - col.label(text="The lattice box is generated facing your view (be sure to face canvas if you want to stay on it)") - col.label(text="Use shortcuts below to deform (a help will be displayed in the topbar)") - - col.separator() - col.label(text="Shortcuts:", icon='HAND') - col.label(text="Spacebar / Enter : Confirm") - col.label(text="Delete / Backspace / Tab(twice) / Ctrl+T : Cancel") - col.label(text="M : Toggle between Linear and Spline mode at any moment") - col.label(text="1-9 top row number : Subdivide the box") - col.label(text="Ctrl + arrows-keys : Subdivide the box incrementally in individual X/Y axis") - - col.separator() - col.label(text="Modes and deformation target:", icon='PIVOT_BOUNDBOX') - col.label(text="- Object mode : The whole GP object is deformed (including all frames)") - col.label(text="- GPencil Edit mode : Deform Selected points") - col.label(text="- Gpencil Paint : Deform last Strokes") - # col.label(text="- Lattice edit : Revive the modal after a ctrl+Z") - - col.separator() - col.label(text="Notes:", icon='TEXT') - col.label(text="- If you return in box deform after applying (with a ctrl+Z), you need to hit 'Ctrl+T' again to revive the modal.") - col.label(text="- A cancel warning will be displayed the first time you hit Tab") +# def box_deform_tuto(layout): +class GPT_MT_box_deform_doc(bpy.types.Menu): + # bl_idname = "OBJECT_MT_custom_menu" + bl_label = "Box Deform Infos Sheet" + def draw(self, context): + layout = self.layout + # call another menu + #layout.operator("wm.call_menu", text="Unwrap").name = "VIEW3D_MT_uv_map" + #**Behavior from context mode** + col = layout.column() + col.label(text='Box Deform Tool') + col.label(text="Usage:", icon='MOD_LATTICE') + col.label(text="Use the shortcut 'Ctrl+T' in available modes (listed below)") + col.label(text="The lattice box is generated facing your view (be sure to face canvas if you want to stay on it)") + col.label(text="Use shortcuts below to deform (a help will be displayed in the topbar)") + + col.separator() + col.label(text="Shortcuts:", icon='HAND') + col.label(text="Spacebar / Enter : Confirm") + col.label(text="Delete / Backspace / Tab(twice) / Ctrl+T : Cancel") + col.label(text="M : Toggle between Linear and Spline mode at any moment") + col.label(text="1-9 top row number : Subdivide the box") + col.label(text="Ctrl + arrows-keys : Subdivide the box incrementally in individual X/Y axis") + + col.separator() + col.label(text="Modes and deformation target:", icon='PIVOT_BOUNDBOX') + col.label(text="- Object mode : The whole GP object is deformed (including all frames)") + col.label(text="- GPencil Edit mode : Deform Selected points") + col.label(text="- Gpencil Paint : Deform last Strokes") + # col.label(text="- Lattice edit : Revive the modal after a ctrl+Z") + + col.separator() + col.label(text="Notes:", icon='TEXT') + col.label(text="- If you return in box deform after applying (with a ctrl+Z), you need to hit 'Ctrl+T' again to revive the modal.") + col.label(text="- A cancel warning will be displayed the first time you hit Tab") ### rotate canvas keymap - addon_keymaps = [] def register_keymaps(): pref = get_addon_prefs() @@ -242,11 +265,11 @@ def unregister_keymaps(): addon_keymaps.clear() - ### REGISTER --- classes = ( GPTS_timeline_settings, + GPT_MT_box_deform_doc, GreasePencilAddonPrefs, ) diff --git a/greasepencil_tools/rotate_canvas.py b/greasepencil_tools/rotate_canvas.py index e2ada531..dcafc2fb 100644 --- a/greasepencil_tools/rotate_canvas.py +++ b/greasepencil_tools/rotate_canvas.py @@ -12,6 +12,16 @@ import blf from gpu_extras.batch import batch_for_shader from gpu_extras.presets import draw_circle_2d +def step_value(value, step): + '''return the step closer to the passed value''' + abs_angle = abs(value) + diff = abs_angle % step + lower_step = abs_angle - diff + higher_step = lower_step + step + if abs_angle - lower_step < higher_step - abs_angle: + return math.copysign(lower_step, value) + else: + return math.copysign(higher_step, value) def draw_callback_px(self, context): # 50% alpha, 2 pixel width line @@ -70,8 +80,9 @@ class RC_OT_RotateCanvas(bpy.types.Operator): self.cam.rotation_mode = self.org_rotation_mode return {'FINISHED'} + def modal(self, context, event): - if event.type in {'MOUSEMOVE','INBETWEEN_MOUSEMOVE'}: + if event.type in {'MOUSEMOVE'}:#,'INBETWEEN_MOUSEMOVE' # Get current mouse coordination (region) self.pos_current = mathutils.Vector((event.mouse_region_x, event.mouse_region_y)) # Get current vector @@ -79,6 +90,19 @@ class RC_OT_RotateCanvas(bpy.types.Operator): # Calculates the angle between initial and current vectors self.angle = self.vector_initial.angle_signed(self.vector_current)#radian # print (math.degrees(self.angle), self.vector_initial, self.vector_current) + + + ## handle snap key + snap = False + if self.snap_ctrl and event.ctrl: + snap = True + if self.snap_shift and event.shift: + snap = True + if self.snap_alt and event.alt: + snap = True + ## Snapping to specific degrees angle + if snap: + self.angle = step_value(self.angle, self.snap_step) if self.in_cam: self.cam.matrix_world = self.cam_matrix @@ -119,7 +143,8 @@ class RC_OT_RotateCanvas(bpy.types.Operator): return {'RUNNING_MODAL'} def invoke(self, context, event): - self.hud = get_addon_prefs().canvas_use_hud + prefs = get_addon_prefs() + self.hud = prefs.canvas_use_hud self.angle = 0.0 self.in_cam = context.region_data.view_perspective == 'CAMERA' @@ -158,6 +183,13 @@ class RC_OT_RotateCanvas(bpy.types.Operator): # Initializes the current vector with the same initial vector. self.vector_current = self.vector_initial.copy() + #Snap keys + self.snap_ctrl = not prefs.use_ctrl + self.snap_shift = not prefs.use_shift + self.snap_alt = not prefs.use_alt + # round to closer degree and convert back to radians + self.snap_step = math.radians(round(math.degrees(prefs.rc_angle_step))) + args = (self, context) if self.hud: self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL') @@ -165,14 +197,59 @@ class RC_OT_RotateCanvas(bpy.types.Operator): return {'RUNNING_MODAL'} +## -- Set / Reset rotation buttons + +class RC_OT_Set_rotation(bpy.types.Operator): + bl_idname = 'view3d.rotate_canvas_set' + bl_label = 'Save Rotation' + bl_description = 'Save active camera rotation (per camera property)' + bl_options = {"REGISTER"} + + @classmethod + def poll(cls, context): + return context.space_data.region_3d.view_perspective == 'CAMERA' + + def execute(self, context): + cam_ob = context.scene.camera + cam_ob['stored_rotation'] = cam_ob.rotation_euler + if not cam_ob.get('_RNA_UI'): + cam_ob['_RNA_UI'] = {} + cam_ob['_RNA_UI']["stored_rotation"] = { + "description":"Stored camera rotation (Gpencil tools > rotate canvas operator)", + "subtype":'EULER', + # "is_overridable_library":0, + } + + return {'FINISHED'} + +class RC_OT_Reset_rotation(bpy.types.Operator): + bl_idname = 'view3d.rotate_canvas_reset' + bl_label = 'Restore Rotation' + bl_description = 'Restore active camera rotation from previously saved state' + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return context.space_data.region_3d.view_perspective == 'CAMERA' and context.scene.camera.get('stored_rotation') + + def execute(self, context): + cam_ob = context.scene.camera + cam_ob.rotation_euler = cam_ob['stored_rotation'] + return {'FINISHED'} + + ### --- REGISTER -def register(): - bpy.utils.register_class(RC_OT_RotateCanvas) +classes = ( + RC_OT_RotateCanvas, + RC_OT_Set_rotation, + RC_OT_Reset_rotation, +) +def register(): + for cls in classes: + bpy.utils.register_class(cls) def unregister(): - bpy.utils.unregister_class(RC_OT_RotateCanvas) - -# if __name__ == "__main__": -# register() + for cls in reversed(classes): + bpy.utils.unregister_class(cls)
\ No newline at end of file diff --git a/greasepencil_tools/ui_panels.py b/greasepencil_tools/ui_panels.py index 292d0e5a..06ad6cb2 100644 --- a/greasepencil_tools/ui_panels.py +++ b/greasepencil_tools/ui_panels.py @@ -41,6 +41,9 @@ class GP_PT_sidebarPanel(bpy.types.Panel): row = layout.row(align=True) row.operator('view3d.zoom_camera_1_to_1', text = 'Zoom 1:1', icon = 'ZOOM_PREVIOUS')# FULLSCREEN_EXIT? row.operator('view3d.view_center_camera', text = 'Zoom Fit', icon = 'FULLSCREEN_ENTER') + row = layout.row(align=True) + row.operator('view3d.rotate_canvas_reset', text = 'Reset Rotation', icon = 'FILE_REFRESH') + row.operator('view3d.rotate_canvas_set', text = 'Save Rotation', icon = 'DRIVER_ROTATIONAL_DIFFERENCE') def menu_boxdeform_entry(self, context): |