From cca9494ae4778424bd84855215c6722b01cd56d5 Mon Sep 17 00:00:00 2001 From: pullusb Date: Tue, 3 Dec 2024 17:23:10 +0100 Subject: [PATCH] backport cursor follow fix from gpv3 - add msg_bus on selection change owned by GreasePencil type 3.3.2 - added: fix cursor follow and add optional target object (backported from gpv3) --- CHANGELOG.md | 7 +++- OP_cursor_snap_canvas.py | 72 ++++++++++++++++++++++++++++++++-------- UI_tools.py | 3 +- __init__.py | 2 +- properties.py | 5 +++ 5 files changed, 72 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ebdca6..b89082f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,15 @@ # Changelog -GPv2 +3.3.2 + +- added: fix cursor follow and add optional target object (backported from gpv3) + 3.3.1 - added: improve file checker and visibility conflict feature (backported from gpv3) +-- GPv2 code - Above this line, version is separated from 4.3+ version (using GPv3) -- + 3.3.0 - added: `Move Material To Layer` has now option to copy instead of moving in pop-up menu. diff --git a/OP_cursor_snap_canvas.py b/OP_cursor_snap_canvas.py index 5c4a85c..ceccfac 100644 --- a/OP_cursor_snap_canvas.py +++ b/OP_cursor_snap_canvas.py @@ -3,6 +3,7 @@ import bpy import mathutils from bpy_extras import view3d_utils from .utils import get_gp_draw_plane, region_to_location, get_view_origin_position +from bpy.app.handlers import persistent ## override all sursor snap shortcut with this in keymap class GPTB_OT_cusor_snap(bpy.types.Operator): @@ -110,15 +111,20 @@ prev_matrix = None # @call_once(bpy.app.handlers.frame_change_post) -def cursor_follow_update(self,context): +## used in properties file to register in boolprop update +def cursor_follow_update(self, context): '''append or remove cursor_follow handler according a boolean''' + ob = bpy.context.object + if bpy.context.scene.gptoolprops.cursor_follow_target: + ## override with target object is specified + ob = bpy.context.scene.gptoolprops.cursor_follow_target global prev_matrix # imported in properties to register in boolprop update if self.cursor_follow:#True + if ob: + # out of below condition to be called when setting target as well + prev_matrix = ob.matrix_world.copy() if not cursor_follow.__name__ in [hand.__name__ for hand in bpy.app.handlers.frame_change_post]: - if context.object: - prev_matrix = context.object.matrix_world - bpy.app.handlers.frame_change_post.append(cursor_follow) else:#False @@ -129,11 +135,13 @@ def cursor_follow_update(self,context): def cursor_follow(scene): '''Handler to make the cursor follow active object matrix changes on frame change''' - ## TODO update global prev_matrix to equal current_matrix on selection change (need another handler)... - if not bpy.context.object: + ob = bpy.context.object + if bpy.context.scene.gptoolprops.cursor_follow_target: + ## override with target object is specified + ob = bpy.context.scene.gptoolprops.cursor_follow_target + if not ob: return global prev_matrix - ob = bpy.context.object current_matrix = ob.matrix_world if not prev_matrix: prev_matrix = current_matrix.copy() @@ -146,9 +154,7 @@ def cursor_follow(scene): ## translation only # scene.cursor.location += (current_matrix - prev_matrix).to_translation() - - # print('offset:', (current_matrix - prev_matrix).to_translation()) - + ## full scene.cursor.location = current_matrix @ (prev_matrix.inverted() @ scene.cursor.location) @@ -156,6 +162,38 @@ def cursor_follow(scene): prev_matrix = current_matrix.copy() +prev_active_obj = None + +## Add check for object selection change +def selection_changed(): + """Callback function for selection changes""" + if not bpy.context.scene.gptoolprops.cursor_follow: + return + if bpy.context.scene.gptoolprops.cursor_follow_target: + # we are following a target, nothing to update on selection change + return + global prev_matrix, prev_active_obj + if prev_active_obj != bpy.context.object: + ## Set stored matrix to active object + prev_matrix = bpy.context.object.matrix_world.copy() + prev_active_obj = bpy.context.object + +## Note: Same owner as layer manager (will be removed as well) +def subscribe_object_change(): + subscribe_to = (bpy.types.LayerObjects, 'active') + bpy.msgbus.subscribe_rna( + key=subscribe_to, + # owner of msgbus subcribe (for clearing later) + owner=bpy.types.GreasePencil, # <-- attach to ID during it's lifetime. + args=(), + notify=selection_changed, + options={'PERSISTENT'}, + ) + +@persistent +def subscribe_object_change_handler(dummy): + subscribe_object_change() + classes = ( GPTB_OT_cusor_snap, ) @@ -163,14 +201,18 @@ GPTB_OT_cusor_snap, def register(): for cls in classes: bpy.utils.register_class(cls) - # swap_keymap_by_id('view3d.cursor3d','view3d.cursor_snap')#auto swap to custom GP snap wrap + + ## Follow cursor matrix update on object change + bpy.app.handlers.load_post.append(subscribe_object_change_handler) # select_change + ## Directly set msgbus to work at first addon activation # select_change + bpy.app.timers.register(subscribe_object_change, first_interval=1) # select_change - # bpy.app.handlers.frame_change_post.append(cursor_follow) + ## No need to frame_change_post.append(cursor_follow). Added by property update, when activating 'cursor follow' def unregister(): - # bpy.app.handlers.frame_change_post.remove(cursor_follow) + bpy.app.handlers.load_post.remove(subscribe_object_change_handler) # select_change # swap_keymap_by_id('view3d.cursor_snap','view3d.cursor3d')#Restore normal snap @@ -179,4 +221,6 @@ def unregister(): # force remove handler if it's there at unregister if cursor_follow.__name__ in [hand.__name__ for hand in bpy.app.handlers.frame_change_post]: - bpy.app.handlers.frame_change_post.remove(cursor_follow) \ No newline at end of file + bpy.app.handlers.frame_change_post.remove(cursor_follow) + + bpy.msgbus.clear_by_owner(bpy.types.GreasePencil) \ No newline at end of file diff --git a/UI_tools.py b/UI_tools.py index b9d5fe6..b4a4782 100644 --- a/UI_tools.py +++ b/UI_tools.py @@ -279,7 +279,8 @@ class GPTB_PT_anim_manager(Panel): col.use_property_split = False text, icon = ('Cursor Follow On', 'PIVOT_CURSOR') if context.scene.gptoolprops.cursor_follow else ('Cursor Follow Off', 'CURSOR') col.prop(context.scene.gptoolprops, 'cursor_follow', text=text, icon=icon) - + if context.scene.gptoolprops.cursor_follow: + col.prop(context.scene.gptoolprops, 'cursor_follow_target', text='Target', icon='OBJECT_DATA') class GPTB_PT_toolbox_playblast(Panel): bl_label = "Playblast" diff --git a/__init__.py b/__init__.py index f19b701..59d806c 100755 --- a/__init__.py +++ b/__init__.py @@ -4,7 +4,7 @@ bl_info = { "name": "GP toolbox", "description": "Tool set for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", -"version": (3, 3, 1), +"version": (3, 3, 2), "blender": (4, 0, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "", diff --git a/properties.py b/properties.py index ba57219..ddbcb87 100755 --- a/properties.py +++ b/properties.py @@ -177,6 +177,11 @@ class GP_PG_ToolsSettings(PropertyGroup): name='Cursor Follow', description="3D cursor follow active object animation when activated", default=False, update=cursor_follow_update) + cursor_follow_target : bpy.props.PointerProperty( + name='Cursor Follow Target', + description="Optional target object to follow for cursor instead of active object", + type=bpy.types.Object, update=cursor_follow_update) + edit_lines_opacity : FloatProperty( name="Edit Lines Opacity", description="Change edit lines opacity for all grease pencils", default=0.5, min=0.0, max=1.0, step=3, precision=2, update=change_edit_lines_opacity)