Add special GP stroke deleter bound to vierw
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
This commit is contained in:
parent
9289b2ed6d
commit
93b7ad91d8
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
4.1.3
|
4.1.3
|
||||||
|
|
||||||
- fixed: error in "remove stroke duplication" using file checker
|
- fixed: error in "remove stroke duplication" using file checker
|
||||||
|
185
OP_delete_viewbound.py
Normal file
185
OP_delete_viewbound.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
from . import utils
|
||||||
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
|
|
||||||
|
def is_stroke_in_view(stroke, matrix, region, region_3d):
|
||||||
|
## for optimization, use first and last
|
||||||
|
region_width = region.width
|
||||||
|
region_height = region.height
|
||||||
|
point_list = [stroke.points[0], stroke.points[-1]]
|
||||||
|
# point_list = stroke.points # whole points
|
||||||
|
|
||||||
|
coord_list = [matrix @ point.position for point in point_list]
|
||||||
|
|
||||||
|
for coord in coord_list:
|
||||||
|
# Convert 3D coordinates to 2D screen coordinates
|
||||||
|
screen_coord = location_3d_to_region_2d(region, region_3d, coord)
|
||||||
|
if screen_coord is None:
|
||||||
|
continue
|
||||||
|
# Check if the point is within the viewport bounds
|
||||||
|
if 0 <= screen_coord.x <= region_width and 0 <= screen_coord.y <= region_height:
|
||||||
|
## one point in view, in view
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class GPT_OT_delete_strokes_view_bound(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.delete_strokes_view_bound"
|
||||||
|
bl_label = "Delete Strokes View Bound "
|
||||||
|
bl_description = "Delete all selected strokes in view only"
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
for layer in layers:
|
||||||
|
for frame in layer.frames:
|
||||||
|
drawing = frame.drawing
|
||||||
|
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)]
|
||||||
|
## detailed method
|
||||||
|
# stroke_indices_to_delete = []
|
||||||
|
# for sid, stroke in enumerate(drawing.strokes):
|
||||||
|
# if not stroke.select:
|
||||||
|
# continue
|
||||||
|
|
||||||
|
# # Check if the stroke is within the view bounds
|
||||||
|
# if is_stroke_in_view(stroke, matrix_world, region, region_3d):
|
||||||
|
# stroke_indices_to_delete.append(sid)
|
||||||
|
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)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class GPT_OT_delete_points_view_bound(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.delete_points_view_bound"
|
||||||
|
bl_label = "Delete Points View Bound"
|
||||||
|
bl_description = "Delete all selected points in view only"
|
||||||
|
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'}
|
||||||
|
|
||||||
|
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 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
|
||||||
|
## 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({'INFO'}, f"Skipped {avoid_ct} strokes not in view")
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
GPT_OT_delete_strokes_view_bound,
|
||||||
|
GPT_OT_delete_points_view_bound,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
if bpy.app.background:
|
||||||
|
return
|
||||||
|
|
||||||
|
for cl in classes:
|
||||||
|
bpy.utils.register_class(cl)
|
||||||
|
|
||||||
|
## make scene property for empty key preservation and bake movement for layers...
|
||||||
|
# register_keymaps()
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
if bpy.app.background:
|
||||||
|
return
|
||||||
|
|
||||||
|
# unregister_keymaps()
|
||||||
|
for cl in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cl)
|
@ -4,7 +4,7 @@ bl_info = {
|
|||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Tool set for Grease Pencil in animation production",
|
"description": "Tool set for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou, Christophe Seux",
|
"author": "Samuel Bernou, Christophe Seux",
|
||||||
"version": (4, 1, 3),
|
"version": (4, 2, 0),
|
||||||
"blender": (4, 3, 0),
|
"blender": (4, 3, 0),
|
||||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
@ -47,6 +47,7 @@ from . import OP_layer_namespace
|
|||||||
from . import OP_pseudo_tint
|
from . import OP_pseudo_tint
|
||||||
from . import OP_follow_curve
|
from . import OP_follow_curve
|
||||||
from . import OP_material_move_to_layer
|
from . import OP_material_move_to_layer
|
||||||
|
from . import OP_delete_viewbound
|
||||||
# from . import OP_eraser_brush
|
# from . import OP_eraser_brush
|
||||||
# from . import TOOL_eraser_brush
|
# from . import TOOL_eraser_brush
|
||||||
from . import handler_draw_cam
|
from . import handler_draw_cam
|
||||||
@ -805,6 +806,7 @@ addon_modules = (
|
|||||||
OP_layer_nav,
|
OP_layer_nav,
|
||||||
OP_follow_curve,
|
OP_follow_curve,
|
||||||
OP_material_move_to_layer,
|
OP_material_move_to_layer,
|
||||||
|
OP_delete_viewbound,
|
||||||
# OP_eraser_brush,
|
# OP_eraser_brush,
|
||||||
# TOOL_eraser_brush, # experimental eraser brush
|
# TOOL_eraser_brush, # experimental eraser brush
|
||||||
handler_draw_cam,
|
handler_draw_cam,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user