From 8b838b52ca5648599cc99640a747f7da244ee980 Mon Sep 17 00:00:00 2001 From: Pullusb Date: Mon, 18 Oct 2021 19:11:17 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 8 +++ OP_material_picker.py | 146 ++++++++++++++++++++++++++++++++++++++++-- __init__.py | 2 +- 3 files changed, 149 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c89f35c..b80e818 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # 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 - fix: reproject GP repeating projection on same frames diff --git a/OP_material_picker.py b/OP_material_picker.py index 017929a..390e34b 100644 --- a/OP_material_picker.py +++ b/OP_material_picker.py @@ -3,6 +3,7 @@ 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 ### passing by 2D projection @@ -23,6 +24,7 @@ def get_3d_coord_on_drawing_plane_from_2d(context, co): return None, None, None +""" class GP_OT_pick_closest_material(Operator): bl_idname = "gp.pick_closest_material" bl_label = "Get Closest Stroke Material" @@ -147,6 +149,142 @@ class GP_OT_pick_closest_material(Operator): # return {'CANCELLED'} # 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 = [] @@ -160,13 +298,9 @@ def register_keymaps(): idname="gp.pick_closest_material", type="S", # type="LEFTMOUSE", value="PRESS", - shift=False, - ctrl=False, - alt = False, - oskey=False, # key_modifier='S', # S like Sample ) - kmi.properties.fill_only = True + kmi.properties.stroke_filter = 'FILL' addon_keymaps.append((km, kmi)) kmi = km.keymap_items.new( @@ -177,7 +311,7 @@ def register_keymaps(): alt = True, # key_modifier='S', # S like Sample ) - + kmi.properties.stroke_filter = 'STROKE' # kmi = km.keymap_items.new('catname.opsname', type='F5', value='PRESS') addon_keymaps.append((km, kmi)) diff --git a/__init__.py b/__init__.py index b573155..4de2f91 100755 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ bl_info = { "name": "GP toolbox", "description": "Set of tools for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", -"version": (1, 6, 8), +"version": (1, 6, 9), "blender": (2, 91, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "",