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
This commit is contained in:
parent
9d30d2bccb
commit
7bc0fa6bf2
|
@ -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": "",
|
||||
|
|
|
@ -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="")
|
||||
|
||||
## 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 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')
|
||||
|
||||
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:')
|
||||
|
||||
## ROTATE CANVAS
|
||||
box = layout.box()
|
||||
box.label(text='Rotate canvas:')
|
||||
box.prop(self, "canvas_use_shortcut", text='Bind Shortcuts')
|
||||
|
||||
box.prop(self, "canvas_use_shortcut", text='Bind Shortcuts')
|
||||
if self.canvas_use_shortcut:
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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)
|
||||
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
|
||||
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')
|
||||
|
||||
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
|
||||
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')
|
||||
|
||||
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)
|
||||
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')
|
||||
|
||||
if self.pref_tabs == 'TUTO':
|
||||
## SCRUB TIMELINE
|
||||
box = layout.box()
|
||||
draw_ts_pref(prefs.ts, box)
|
||||
|
||||
#**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")
|
||||
# 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"
|
||||
|
||||
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")
|
||||
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="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")
|
||||
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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue