paint mode precise layer picker on W
1.7.4 - added: Pick layer from closest stroke in paint mode using quick press on `W` for stroke (and `alt+W` for fills)gpv2
parent
a0ed941e4b
commit
56cbc04c65
|
@ -1,5 +1,9 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
1.7.4
|
||||||
|
|
||||||
|
- added: Pick layer from closest stroke in paint mode using quick press on `W` for stroke (and `alt+W` for fills)
|
||||||
|
|
||||||
1.7.3
|
1.7.3
|
||||||
|
|
||||||
- added: show/hide gp modifiers if they are showed in render (in subpanel `Animation Manager`)
|
- added: show/hide gp modifiers if they are showed in render (in subpanel `Animation Manager`)
|
||||||
|
@ -30,7 +34,7 @@
|
||||||
|
|
||||||
1.6.9
|
1.6.9
|
||||||
|
|
||||||
- change: material picker (`S` and `Alt+S`) quick trigger, cahnge is only triggered if ley is pressed less than 200ms
|
- change: material picker (`S` and `Alt+S`) quick trigger, change is only triggered if key is pressed less than 200ms
|
||||||
- this is made to let other operator use functionality on long press using `S`
|
- this is made to let other operator use functionality on long press using `S`
|
||||||
- feat: material picker shortcut has now an enum choice to filter targeted stroke (fill, stroke, all)
|
- feat: material picker shortcut has now an enum choice to filter targeted stroke (fill, stroke, all)
|
||||||
- by default `S` is still fill only
|
- by default `S` is still fill only
|
||||||
|
|
|
@ -715,6 +715,9 @@ GPCLIP_PT_clipboard_ui,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
if bpy.app.background:
|
||||||
|
return
|
||||||
|
|
||||||
for cl in classes:
|
for cl in classes:
|
||||||
bpy.utils.register_class(cl)
|
bpy.utils.register_class(cl)
|
||||||
|
|
||||||
|
@ -722,6 +725,9 @@ def register():
|
||||||
register_keymaps()
|
register_keymaps()
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
if bpy.app.background:
|
||||||
|
return
|
||||||
|
|
||||||
unregister_keymaps()
|
unregister_keymaps()
|
||||||
for cl in reversed(classes):
|
for cl in reversed(classes):
|
||||||
bpy.utils.unregister_class(cl)
|
bpy.utils.unregister_class(cl)
|
||||||
|
|
|
@ -0,0 +1,161 @@
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
import mathutils
|
||||||
|
from mathutils import Vector, Matrix, geometry
|
||||||
|
from bpy_extras import view3d_utils
|
||||||
|
from time import time
|
||||||
|
from .utils import get_gp_draw_plane, location_to_region, region_to_location
|
||||||
|
|
||||||
|
class GP_OT_pick_closest_layer(Operator):
|
||||||
|
bl_idname = "gp.pick_closest_layer"
|
||||||
|
bl_label = "Active Closest Stroke Layer"
|
||||||
|
bl_description = "Pick closest stroke layer"
|
||||||
|
bl_options = {"REGISTER"} # , "UNDO"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.object and context.object.type == 'GPENCIL' and context.mode == 'PAINT_GPENCIL'
|
||||||
|
|
||||||
|
stroke_filter : bpy.props.EnumProperty(default='STROKE',
|
||||||
|
items=(
|
||||||
|
('STROKE', 'Stroke', 'Target only Stroke', 0),
|
||||||
|
('FILL', 'Fill', 'Target only Fill', 1),
|
||||||
|
('ALL', 'All', 'All stroke types', 2),
|
||||||
|
),
|
||||||
|
options={'SKIP_SAVE'})
|
||||||
|
|
||||||
|
def filter_stroke(self, context):
|
||||||
|
kd = mathutils.kdtree.KDTree(len(self.point_pair))
|
||||||
|
for i, pair in enumerate(self.point_pair):
|
||||||
|
kd.insert(pair[0], i)
|
||||||
|
kd.balance()
|
||||||
|
|
||||||
|
mouse_vec3 = Vector((*self.init_mouse, 0))
|
||||||
|
co, index, _dist = kd.find(mouse_vec3)
|
||||||
|
lid = self.point_pair[index][1]
|
||||||
|
return lid
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
self.t0 = time()
|
||||||
|
self.limit = self.t0 + 0.2 # 200 miliseconds
|
||||||
|
self.init_mouse = Vector((event.mouse_region_x, event.mouse_region_y))
|
||||||
|
self.idx = None
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
if time() > self.limit:
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
if event.value == 'RELEASE': # if a key was release (any key in case shortcut was customised)
|
||||||
|
if time() > self.limit:
|
||||||
|
# dont know if condition is neeed
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
return self.execute(context)
|
||||||
|
# return {'FINISHED'}
|
||||||
|
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
# return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
t1 = time()
|
||||||
|
# self.prefs = get_addon_prefs()
|
||||||
|
self.ob = context.object
|
||||||
|
mat = self.ob.matrix_world
|
||||||
|
gp = self.ob.data
|
||||||
|
|
||||||
|
self.inv_mat = self.ob.matrix_world.inverted()
|
||||||
|
self.point_pair = []
|
||||||
|
if gp.use_multiedit:
|
||||||
|
for layer_id, l in enumerate(gp.layers):
|
||||||
|
if l.hide:# l.lock or
|
||||||
|
continue
|
||||||
|
for f in l.frames:
|
||||||
|
if not f.select:
|
||||||
|
continue
|
||||||
|
for s in f.strokes:
|
||||||
|
if self.stroke_filter == 'STROKE' and not self.ob.data.materials[s.material_index].grease_pencil.show_stroke:
|
||||||
|
continue
|
||||||
|
elif self.stroke_filter == 'FILL' and not self.ob.data.materials[s.material_index].grease_pencil.show_fill:
|
||||||
|
continue
|
||||||
|
self.point_pair += [(Vector((*location_to_region(mat @ p.co), 0)), layer_id) for p in s.points]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# [s for l in gp.layers if not l.lock and not l.hide for s in l.active_frame.stokes]
|
||||||
|
for layer_id, l in enumerate(gp.layers):
|
||||||
|
if l.hide or not l.active_frame:# l.lock or
|
||||||
|
continue
|
||||||
|
for s in l.active_frame.strokes:
|
||||||
|
if self.stroke_filter == 'STROKE' and not self.ob.data.materials[s.material_index].grease_pencil.show_stroke:
|
||||||
|
continue
|
||||||
|
elif self.stroke_filter == 'FILL' and not self.ob.data.materials[s.material_index].grease_pencil.show_fill:
|
||||||
|
continue
|
||||||
|
self.point_pair += [(Vector((*location_to_region(mat @ p.co), 0)), layer_id) for p in s.points]
|
||||||
|
|
||||||
|
if not self.point_pair:
|
||||||
|
self.report({'ERROR'}, 'No stroke found, maybe layers are locked or hidden')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
|
||||||
|
lid = self.filter_stroke(context)
|
||||||
|
if isinstance(lid, str):
|
||||||
|
self.report({'ERROR'}, lid)
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
del self.point_pair # auto garbage collected ?
|
||||||
|
|
||||||
|
self.ob.data.layers.active_index = lid
|
||||||
|
|
||||||
|
## debug show trigger time
|
||||||
|
# print(f'Trigger time {time() - self.t0:.3f}')
|
||||||
|
# print(f'Search time {time() - t1:.3f}')
|
||||||
|
self.report({'INFO'}, f'Layer: {self.ob.data.layers.active.info}')
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
addon_keymaps = []
|
||||||
|
def register_keymaps():
|
||||||
|
addon = bpy.context.window_manager.keyconfigs.addon
|
||||||
|
km = addon.keymaps.new(name = "Grease Pencil Stroke Paint Mode", space_type = "EMPTY", region_type='WINDOW')
|
||||||
|
|
||||||
|
kmi = km.keymap_items.new(
|
||||||
|
# name="",
|
||||||
|
idname="gp.pick_closest_layer",
|
||||||
|
type="W",
|
||||||
|
value="PRESS",
|
||||||
|
)
|
||||||
|
kmi.properties.stroke_filter = 'STROKE'
|
||||||
|
addon_keymaps.append((km, kmi))
|
||||||
|
|
||||||
|
kmi = km.keymap_items.new(
|
||||||
|
# name="",
|
||||||
|
idname="gp.pick_closest_layer",
|
||||||
|
type="W",
|
||||||
|
value="PRESS",
|
||||||
|
alt = True,
|
||||||
|
)
|
||||||
|
kmi.properties.stroke_filter = 'FILL'
|
||||||
|
# kmi = km.keymap_items.new('catname.opsname', type='F5', value='PRESS')
|
||||||
|
addon_keymaps.append((km, kmi))
|
||||||
|
|
||||||
|
def unregister_keymaps():
|
||||||
|
for km, kmi in addon_keymaps:
|
||||||
|
km.keymap_items.remove(kmi)
|
||||||
|
addon_keymaps.clear()
|
||||||
|
|
||||||
|
|
||||||
|
classes=(
|
||||||
|
GP_OT_pick_closest_layer,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
register_keymaps()
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
unregister_keymaps()
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
|
@ -15,7 +15,7 @@ bl_info = {
|
||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Set of tools for Grease Pencil in animation production",
|
"description": "Set of tools for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou, Christophe Seux",
|
"author": "Samuel Bernou, Christophe Seux",
|
||||||
"version": (1, 7, 3),
|
"version": (1, 7, 4),
|
||||||
"blender": (2, 91, 0),
|
"blender": (2, 91, 0),
|
||||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
@ -48,6 +48,7 @@ from . import OP_realign
|
||||||
from . import OP_depth_move
|
from . import OP_depth_move
|
||||||
from . import OP_key_duplicate_send
|
from . import OP_key_duplicate_send
|
||||||
from . import OP_layer_manager
|
from . import OP_layer_manager
|
||||||
|
from . import OP_stroke_picker
|
||||||
from . import OP_material_picker
|
from . import OP_material_picker
|
||||||
from . import OP_eraser_brush
|
from . import OP_eraser_brush
|
||||||
from . import TOOL_eraser_brush
|
from . import TOOL_eraser_brush
|
||||||
|
@ -579,6 +580,7 @@ def register():
|
||||||
OP_layer_manager.register()
|
OP_layer_manager.register()
|
||||||
OP_eraser_brush.register()
|
OP_eraser_brush.register()
|
||||||
OP_material_picker.register()
|
OP_material_picker.register()
|
||||||
|
OP_stroke_picker.register()
|
||||||
TOOL_eraser_brush.register()
|
TOOL_eraser_brush.register()
|
||||||
handler_draw_cam.register()
|
handler_draw_cam.register()
|
||||||
UI_tools.register()
|
UI_tools.register()
|
||||||
|
@ -604,9 +606,10 @@ def unregister():
|
||||||
bpy.utils.unregister_class(cls)
|
bpy.utils.unregister_class(cls)
|
||||||
UI_tools.unregister()
|
UI_tools.unregister()
|
||||||
handler_draw_cam.unregister()
|
handler_draw_cam.unregister()
|
||||||
|
TOOL_eraser_brush.unregister()
|
||||||
|
OP_stroke_picker.unregister()
|
||||||
OP_material_picker.unregister()
|
OP_material_picker.unregister()
|
||||||
OP_eraser_brush.unregister()
|
OP_eraser_brush.unregister()
|
||||||
TOOL_eraser_brush.unregister()
|
|
||||||
OP_layer_manager.unregister()
|
OP_layer_manager.unregister()
|
||||||
OP_key_duplicate_send.unregister()
|
OP_key_duplicate_send.unregister()
|
||||||
OP_depth_move.unregister()
|
OP_depth_move.unregister()
|
||||||
|
|
Loading…
Reference in New Issue