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, is_locked, is_hidden class GP_OT_pick_closest_layer(Operator): bl_idname = "gp.pick_closest_layer" bl_label = "Get 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 == 'GREASEPENCIL' and context.mode == 'PAINT_GREASE_PENCIL' stroke_filter : bpy.props.EnumProperty(name='Target', 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) layer = self.point_pair[index][1] return layer 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 draw(self, context): layout = self.layout if context.object.data.layers.active: layout.label(text=f'Layer: {context.object.data.layers.active.name}') layout.prop(self, 'stroke_filter') 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 context.scene.tool_settings.use_grease_pencil_multi_frame_editing: for layer in gp.layers: if is_hidden(layer): continue for f in layer.frames: if not f.select: continue for s in f.drawing.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.position), 0)), layer) for p in s.points] else: # [s for l in gp.layers if not is_locked(l) and not is_hidden(l) for s in l.current_frame().stokes] for layer in gp.layers: if is_hidden(layer) or not layer.current_frame(): continue for s in layer.current_frame().drawing.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.position), 0)), layer) for p in s.points] if not self.point_pair: self.report({'ERROR'}, 'No stroke found, maybe layers are locked or hidden') return {'CANCELLED'} layer_target = self.filter_stroke(context) if isinstance(layer_target, str): self.report({'ERROR'}, layer_target) return {'CANCELLED'} del self.point_pair # auto garbage collected ? self.ob.data.layers.active = layer_target ## 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.name}') return {'FINISHED'} addon_keymaps = [] def register_keymaps(): addon = bpy.context.window_manager.keyconfigs.addon km = addon.keymaps.new(name = "Grease Pencil 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)