material pick with quick press and add enum filter

1.6.9

- change: material picker (`S` and `Alt+S`) quick trigger, cahnge is only triggered if ley is pressed less than 200ms
  - 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)
  - by default `S` is still fill only
  - but `Alt+S` is now stroke only instead of all
gpv2
Pullusb 2021-10-18 19:11:17 +02:00
parent d4ea39fb40
commit 8b838b52ca
3 changed files with 149 additions and 7 deletions

View File

@ -1,5 +1,13 @@
# Changelog # Changelog
1.6.9
- change: material picker (`S` and `Alt+S`) quick trigger, cahnge is only triggered if ley is pressed less than 200ms
- 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)
- by default `S` is still fill only
- but `Alt+S` is now stroke only instead of all
1.6.8 1.6.8
- fix: reproject GP repeating projection on same frames - fix: reproject GP repeating projection on same frames

View File

@ -3,6 +3,7 @@ from bpy.types import Operator
import mathutils import mathutils
from mathutils import Vector, Matrix, geometry from mathutils import Vector, Matrix, geometry
from bpy_extras import view3d_utils from bpy_extras import view3d_utils
from time import time
from .utils import get_gp_draw_plane, location_to_region, region_to_location from .utils import get_gp_draw_plane, location_to_region, region_to_location
### passing by 2D projection ### passing by 2D projection
@ -23,6 +24,7 @@ def get_3d_coord_on_drawing_plane_from_2d(context, co):
return None, None, None return None, None, None
"""
class GP_OT_pick_closest_material(Operator): class GP_OT_pick_closest_material(Operator):
bl_idname = "gp.pick_closest_material" bl_idname = "gp.pick_closest_material"
bl_label = "Get Closest Stroke Material" bl_label = "Get Closest Stroke Material"
@ -147,6 +149,142 @@ class GP_OT_pick_closest_material(Operator):
# return {'CANCELLED'} # return {'CANCELLED'}
# return {'RUNNING_MODAL'} # return {'RUNNING_MODAL'}
"""
class GP_OT_pick_closest_material(Operator):
bl_idname = "gp.pick_closest_material"
bl_label = "Get Closest Stroke Material"
bl_description = "Pick closest stroke material"
bl_options = {"REGISTER"} # , "UNDO"
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL' and context.mode == 'PAINT_GPENCIL'
# fill_only : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'})
stroke_filter : bpy.props.EnumProperty(default='FILL',
items=(
('FILL', 'Fill', 'Target only Fill materials', 0),
('STROKE', 'Stroke', 'Target only Stroke materials', 1),
('ALL', 'All', 'All material', 2),
),
options={'SKIP_SAVE'})
def filter_stroke(self, context):
# get stroke under mouse using kdtree
point_pair = [(p.co, s) for s in self.stroke_list for p in s.points] # local space
kd = mathutils.kdtree.KDTree(len(point_pair))
for i, pair in enumerate(point_pair):
kd.insert(pair[0], i)
kd.balance()
## Get 3D coordinate on drawing plane according to mouse 2d.co on flat 2d drawing
_ob, hit, _plane_no = get_3d_coord_on_drawing_plane_from_2d(context, self.init_mouse)
if not hit:
return 'No hit on drawing plane', None
mouse_3d = hit
mouse_local = self.inv_mat @ mouse_3d # local space
co, index, _dist = kd.find(mouse_local) # local space
# co, index, _dist = kd.find(mouse_3d) # world space
# context.scene.cursor.location = co # world space
s = point_pair[index][1]
## find point index in stroke
self.idx = None
for i, p in enumerate(s.points):
if p.co == co:
self.idx = i
break
del point_pair
return s, self.ob.matrix_world @ co
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):
# self.prefs = get_addon_prefs()
self.ob = context.object
gp = self.ob.data
self.stroke_list = []
self.inv_mat = self.ob.matrix_world.inverted()
if gp.use_multiedit:
for l in gp.layers:
if l.hide:# l.lock or
continue
for f in l.frames:
if not f.select:
continue
for s in f.strokes:
self.stroke_list.append(s)
else:
# [s for l in gp.layers if not l.lock and not l.hide for s in l.active_frame.stokes]
for l in gp.layers:
if l.hide or not l.active_frame:# l.lock or
continue
for s in l.active_frame.strokes:
self.stroke_list.append(s)
if self.stroke_filter == 'FILL':
self.stroke_list = [s for s in self.stroke_list if self.ob.data.materials[s.material_index].grease_pencil.show_fill]
elif self.stroke_filter == 'STROKE':
self.stroke_list = [s for s in self.stroke_list if self.ob.data.materials[s.material_index].grease_pencil.show_stroke]
# else ALL (no filter)
if not self.stroke_list:
self.report({'ERROR'}, 'No stroke found, maybe layers are locked or hidden')
return {'CANCELLED'}
stroke, self.coord = self.filter_stroke(context)
if isinstance(stroke, str):
self.report({'ERROR'}, stroke)
return {'CANCELLED'}
del self.stroke_list
if self.idx is None:
self.report({'WARNING'}, 'No coord found')
return {'CANCELLED'}
# self.depth = self.ob.matrix_world @ stroke.points[self.idx].co
# self.init_pos = [p.co.copy() for p in stroke.points] # need a copy otherwise vector is updated
# self.pos_2d = [location_to_region(self.ob.matrix_world @ co) for co in self.init_pos]
# self.plen = len(stroke.points)
self.ob.active_material_index = stroke.material_index
## debug show trigger time
# print(f'Trigger time {time() - self.t0:.3f}')
self.report({'INFO'}, f'Mat: {self.ob.data.materials[stroke.material_index].name}')
return {'FINISHED'}
addon_keymaps = [] addon_keymaps = []
@ -160,13 +298,9 @@ def register_keymaps():
idname="gp.pick_closest_material", idname="gp.pick_closest_material",
type="S", # type="LEFTMOUSE", type="S", # type="LEFTMOUSE",
value="PRESS", value="PRESS",
shift=False,
ctrl=False,
alt = False,
oskey=False,
# key_modifier='S', # S like Sample # key_modifier='S', # S like Sample
) )
kmi.properties.fill_only = True kmi.properties.stroke_filter = 'FILL'
addon_keymaps.append((km, kmi)) addon_keymaps.append((km, kmi))
kmi = km.keymap_items.new( kmi = km.keymap_items.new(
@ -177,7 +311,7 @@ def register_keymaps():
alt = True, alt = True,
# key_modifier='S', # S like Sample # key_modifier='S', # S like Sample
) )
kmi.properties.stroke_filter = 'STROKE'
# kmi = km.keymap_items.new('catname.opsname', type='F5', value='PRESS') # kmi = km.keymap_items.new('catname.opsname', type='F5', value='PRESS')
addon_keymaps.append((km, kmi)) addon_keymaps.append((km, kmi))

View File

@ -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, 6, 8), "version": (1, 6, 9),
"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": "",