gp_toolbox/OP_cursor_snap_canvas.py

182 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

## snap 3D cursor on active grease pencil object canvas surfaces
import bpy
import mathutils
from bpy_extras import view3d_utils
from .utils import get_gp_draw_plane, region_to_location, get_view_origin_position
## override all sursor snap shortcut with this in keymap
class GPTB_OT_cusor_snap(bpy.types.Operator):
bl_idname = "view3d.cusor_snap"
bl_label = "Snap cursor to GP"
bl_description = "Snap 3d cursor to active GP object canvas (else use normal place)"
bl_options = {"REGISTER"}#, "INTERNAL"
# @classmethod
# def poll(cls, context):
# return context.object and context.object.type == 'GREASEPENCIL'
def invoke(self, context, event):
#print('-!SNAP!-')
self.mouse_co = mathutils.Vector((event.mouse_region_x, event.mouse_region_y))
# print('self.mouse_co: ', self.mouse_co)
self.execute(context)
return {"FINISHED"}
def execute(self, context):
if not context.object or context.object.type != 'GREASEPENCIL':
self.report({'INFO'}, 'Not GP, Cursor surface project')
bpy.ops.view3d.cursor3d('INVOKE_DEFAULT', use_depth=True, orientation='NONE')#'NONE', 'VIEW', 'XFORM', 'GEOM'
return {"FINISHED"}
if context.region_data.view_perspective == 'ORTHO':
bpy.ops.view3d.cursor3d('INVOKE_DEFAULT', use_depth=True, orientation='NONE')#'NONE', 'VIEW', 'XFORM', 'GEOM'
self.report({'WARNING'}, 'Ortholinear ! not snaped to GP plane Cursor surface project)')
return {"FINISHED"}
self.report({'INFO'}, 'Using GP picking')
settings = context.scene.tool_settings
orient = settings.gpencil_sculpt.lock_axis#'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
loc = settings.gpencil_stroke_placement_view3d#'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
warning = []
if not "AXIS" in orient:
warning.append(f'Orientation is {orient}, no depth picking')
if loc != "ORIGIN":
warning.append(f"Location is '{loc}' not object 'ORIGIN'")
if warning:
self.report({'WARNING'}, ', '.join(warning))
plane_co, plane_no = get_gp_draw_plane()
if not plane_co:#default to object location
plane_co = context.object.matrix_world.to_translation()#context.object.location
if not plane_no:# use view depth (region_to_location instead of )
coord = region_to_location(self.mouse_co, plane_co)
else:
#projected on given plane from view (intersect on plane with a vector from view origin)
origin = get_view_origin_position()#get view origin
region = bpy.context.region
rv3d = bpy.context.region_data
coord = mathutils.geometry.intersect_line_plane(origin, origin - view3d_utils.region_2d_to_vector_3d(region, rv3d, self.mouse_co), plane_co, plane_no)
# If no plane is crossed, intersect_line_plane return None which naturally goes to traceback...
if not coord:
self.report({'WARNING'}, 'Ortholinear view, used basic cursor snap (no depth picking)')
context.scene.cursor.location = coord
return {"FINISHED"}
#TODO auto-cursor (attach cursor to object)
''' cursor native snap
https://docs.blender.org/api/current/bpy.ops.view3d.html#bpy.ops.view3d.cursor3d
bpy.ops.view3d.cursor3d(use_depth=True, orientation='VIEW')
Set the location of the 3D cursor
Parameters
use_depth (boolean, (optional)) Surface Project, Project onto the surface
orientation (enum in ['NONE', 'VIEW', 'XFORM', 'GEOM'], (optional))
Orientation, Preset viewpoint to use
NONE None, Leave orientation unchanged.
VIEW View, Orient to the viewport.
XFORM Transform, Orient to the current transform setting.
GEOM Geometry, Match the surface normal.
'''
def swap_keymap_by_id(org_idname, new_idname):
'''Replace id operator by another in user keymap'''
wm = bpy.context.window_manager
for cat, keymap in wm.keyconfigs.user.keymaps.items():#wm.keyconfigs.addon.keymaps.items():
for k in keymap.keymap_items:
if k.idname != org_idname:
continue
## Print changes
mods = ' + '.join([m for m in ('ctrl','shift','alt') if getattr(k, m)])
val = f' ({k.value.lower()})' if k.value != 'PRESS' else ''
# ({keymap.space_type}) #VIEW_3D
print(f"Hotswap: {cat} - {k.name}: {mods + ' ' if mods else ''}{k.type}{val} : {k.idname} --> {new_idname}")
k.idname = new_idname
# prev_matrix = mathutils.Matrix()
prev_matrix = None
# @call_once(bpy.app.handlers.frame_change_post)
def cursor_follow_update(self,context):
'''append or remove cursor_follow handler according a boolean'''
global prev_matrix
# imported in properties to register in boolprop update
if self.cursor_follow:#True
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
if cursor_follow.__name__ in [hand.__name__ for hand in bpy.app.handlers.frame_change_post]:
prev_matrix = None
bpy.app.handlers.frame_change_post.remove(cursor_follow)
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:
return
global prev_matrix
ob = bpy.context.object
current_matrix = ob.matrix_world
if not prev_matrix:
prev_matrix = current_matrix.copy()
return
# debug prints : HANDLER CALLED TWICE in time line when clic (clic press, and clic release)!!!
# print(scene.frame_current)
# print('prev: ', [[f'{j:.2f}' for j in i] for i in prev_matrix[:2] ])
# print('curr: ', [[f'{j:.2f}' for j in i] for i in current_matrix[:2] ])
## 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)
# store for next use
prev_matrix = current_matrix.copy()
classes = (
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
# bpy.app.handlers.frame_change_post.append(cursor_follow)
def unregister():
# bpy.app.handlers.frame_change_post.remove(cursor_follow)
# swap_keymap_by_id('view3d.cursor_snap','view3d.cursor3d')#Restore normal snap
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
# 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)