From a86a1fd4097deca933e221cab53c70f9f9eba0cd Mon Sep 17 00:00:00 2001 From: pullusb Date: Tue, 29 Jul 2025 18:24:04 +0200 Subject: [PATCH] merge stroke-point del into one ops - add in menu --- CHANGELOG.md | 8 +-- OP_delete_viewbound.py | 114 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c428572..af3bf91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,11 @@ 4.2.0 - added: Delete Grease pencil strokes or points view bound (selection outside of viewport region is untouched) - - No preset shortcut: - - `gp.delete_points_view_bound` for points - - `gp.delete_strokes_view_bound` for strokes + - No preset shortcut + + + - `grease_pencil.delete_view_bound` + - Added to Delete menu (au) 4.1.3 diff --git a/OP_delete_viewbound.py b/OP_delete_viewbound.py index 8f14016..da434bb 100644 --- a/OP_delete_viewbound.py +++ b/OP_delete_viewbound.py @@ -24,9 +24,9 @@ def is_stroke_in_view(stroke, matrix, region, region_3d): return False - -class GPT_OT_delete_strokes_view_bound(bpy.types.Operator): - bl_idname = "gp.delete_strokes_view_bound" +""" # separated operators for strokes and points +class GP_OT_delete_strokes_view_bound(bpy.types.Operator): + bl_idname = "grease_pencil.delete_strokes_view_bound" bl_label = "Delete Strokes View Bound " bl_description = "Delete all selected strokes in view only" bl_options = {'REGISTER', 'UNDO'} @@ -77,8 +77,8 @@ class GPT_OT_delete_strokes_view_bound(bpy.types.Operator): return {'FINISHED'} -class GPT_OT_delete_points_view_bound(bpy.types.Operator): - bl_idname = "gp.delete_points_view_bound" +class GP_OT_delete_points_view_bound(bpy.types.Operator): + bl_idname = "grease_pencil.delete_points_view_bound" bl_label = "Delete Points View Bound" bl_description = "Delete all selected points in view only" bl_options = {'REGISTER', 'UNDO'} @@ -155,15 +155,111 @@ class GPT_OT_delete_points_view_bound(bpy.types.Operator): drawing.remove_strokes(indices=stroke_indices_to_delete) if avoid_ct: - self.report({'INFO'}, f"Skipped {avoid_ct} strokes not in view") + self.report({'WARNING'}, f"Skipped {avoid_ct} strokes not in view") + return {'FINISHED'} +""" +class GP_OT_delete_view_bound(bpy.types.Operator): + bl_idname = "grease_pencil.delete_view_bound" + bl_label = "Delete View Bound" + bl_description = "Delete all selected strokes / points only if there are in view (current viewport region)" + bl_options = {'REGISTER', 'UNDO'} + + # def invoke(self, context, event): + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_GREASE_PENCIL' + + def execute(self, context): + gp = context.grease_pencil + if not gp: + self.report({'ERROR'}, "No Grease Pencil object found") + return {'CANCELLED'} + + ob = context.object + if not ob or ob.type != 'GREASEPENCIL': + self.report({'ERROR'}, "Active object is not a Grease Pencil object") + return {'CANCELLED'} + + select_mode = context.scene.tool_settings.gpencil_selectmode_edit + matrix_world = ob.matrix_world.copy() + + layers = [l for l in gp.layers if not l.hide and not l.lock] + + region = context.region + region_3d = context.space_data.region_3d + + ## TODO for frames, handle multiframe editing + + avoid_ct = 0 + for layer in layers: + for frame in layer.frames: + drawing = frame.drawing + + stroke_indices_to_delete = [] + ## For stroke mode + # stroke_indices_to_delete = [sidx for sidx, stroke in enumerate(drawing.strokes) if stroke.select and is_stroke_in_view(stroke, matrix_world, region, region_3d)] + for sid, stroke in enumerate(drawing.strokes): + if not stroke.select: + continue + + # Check if the stroke is within the view bounds + if not is_stroke_in_view(stroke, matrix_world, region, region_3d): + avoid_ct += 1 + continue + + ## No need for further check if we are in stroke mod (direct delete) + if select_mode == 'STROKE': + stroke_indices_to_delete.append(sid) + continue + + ## --- Handle points --- + + ## Dissolve mode remove points (only remove by an amount from the end . need to rebuild the stroke !) + select_count = len([p for p in stroke.points if p.select]) + if select_count == len(stroke.points): + ## mark as full delete and go next + stroke_indices_to_delete.append(sid) + continue + + + ## Make a copy of deselected points attributes + point_attrs = [] + for p in stroke.points: + if p.select: + continue + point_attrs.append([p.position.copy(), p.rotation, p.radius, p.opacity]) + + ## remove as many points as select_count + stroke.remove_points(select_count) + + ## re-assign attributes in order + for i, p in enumerate(stroke.points): + if i < len(point_attrs): + p.position = point_attrs[i][0] + p.rotation = point_attrs[i][1] + p.radius = point_attrs[i][2] + p.opacity = point_attrs[i][3] + p.select = False # Deselect all points after processing + + if stroke_indices_to_delete: + # print(f'layer {layer.name} : delete {len(stroke_indices_to_delete)} strokes') + drawing.remove_strokes(indices=stroke_indices_to_delete) + + if avoid_ct: + self.report({'WARNING'}, f"Skipped {avoid_ct} out of view") return {'FINISHED'} +def draw_delete_view_bound_ui(self, context): + layout = self.layout + layout.operator('grease_pencil.delete_view_bound', text='Delete View bound', icon='LOCKVIEW_ON') # RESTRICT_VIEW_ON classes = ( - GPT_OT_delete_strokes_view_bound, - GPT_OT_delete_points_view_bound, + # GP_OT_delete_strokes_view_bound, + # GP_OT_delete_points_view_bound, + GP_OT_delete_view_bound, ) def register(): @@ -172,6 +268,7 @@ def register(): for cl in classes: bpy.utils.register_class(cl) + bpy.types.VIEW3D_MT_edit_greasepencil_delete.append(draw_delete_view_bound_ui) ## make scene property for empty key preservation and bake movement for layers... # register_keymaps() @@ -180,6 +277,7 @@ def unregister(): if bpy.app.background: return + bpy.types.VIEW3D_MT_edit_greasepencil_delete.remove(draw_delete_view_bound_ui) # unregister_keymaps() for cl in reversed(classes): bpy.utils.unregister_class(cl)