Utility_Apps/Blender/addons/Draw Selection Outline/__init__.py

212 lines
No EOL
7.9 KiB
Python

bl_info = {
"name": "Selection Outline",
"author": "ChatGPT + Ryan",
"version": (1, 0),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > Outline",
"description": "Draw a custom outline around selected objects",
"category": "3D View",
}
import bpy
import gpu
import bmesh
from gpu_extras.batch import batch_for_shader
from mathutils import Vector
from bpy.app.handlers import persistent
def draw_callback():
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
gpu.state.blend_set('ALPHA')
gpu.state.line_width_set(bpy.context.window_manager.outline_thickness)
gpu.state.depth_test_set('NONE')
selected = bpy.context.selected_objects
depsgraph = bpy.context.evaluated_depsgraph_get()
color_obj = bpy.context.window_manager.outline_color
for obj in selected:
if obj.type == 'MESH':
if bpy.context.mode == 'EDIT_MESH' and obj == bpy.context.active_object:
bm = bmesh.from_edit_mesh(obj.data)
edges = [e for e in bm.edges if e.select]
if not edges:
continue
coords = []
for e in edges:
v1 = obj.matrix_world @ e.verts[0].co
v2 = obj.matrix_world @ e.verts[1].co
coords.extend([v1, v2])
shader.bind()
shader.uniform_float("color", color_obj)
batch = batch_for_shader(shader, 'LINES', {"pos": coords})
batch.draw(shader)
else:
eval_obj = obj.evaluated_get(depsgraph)
mesh = eval_obj.to_mesh()
if not mesh:
continue
coords = [obj.matrix_world @ v.co for v in mesh.vertices]
edges = [(e.vertices[0], e.vertices[1]) for e in mesh.edges]
verts = [coords[i] for edge in edges for i in edge]
shader.bind()
shader.uniform_float("color", color_obj)
batch = batch_for_shader(shader, 'LINES', {"pos": verts})
batch.draw(shader)
eval_obj.to_mesh_clear()
elif obj.type in {'CURVE', 'SURFACE', 'FONT'}:
eval_obj = obj.evaluated_get(depsgraph)
mesh = eval_obj.to_mesh()
if not mesh:
continue
coords = [obj.matrix_world @ v.co for v in mesh.vertices]
edges = [(e.vertices[0], e.vertices[1]) for e in mesh.edges]
verts = [coords[i] for edge in edges for i in edge]
shader.bind()
shader.uniform_float("color", color_obj)
batch = batch_for_shader(shader, 'LINES', {"pos": verts})
batch.draw(shader)
eval_obj.to_mesh_clear()
elif obj.type == 'EMPTY':
size = obj.empty_display_size
mat = obj.matrix_world
half = size * 0.5
corners = [Vector((x, y, z)) for x in (-half, half) for y in (-half, half) for z in (-half, half)]
coords = [mat @ corner for corner in corners]
edges = [
(0, 1), (0, 2), (0, 4),
(1, 3), (1, 5),
(2, 3), (2, 6),
(3, 7),
(4, 5), (4, 6),
(5, 7),
(6, 7),
]
verts = [coords[i] for edge in edges for i in edge]
shader.bind()
shader.uniform_float("color", color_obj)
batch = batch_for_shader(shader, 'LINES', {"pos": verts})
batch.draw(shader)
class VIEW3D_OT_draw_selection_outline(bpy.types.Operator):
"""Toggle selection outline"""
bl_idname = "view3d.draw_selection_outline"
bl_label = "Toggle Outline Drawing"
bl_options = {'REGISTER'}
_handle = None
def execute(self, context):
wm = context.window_manager
if wm.draw_selection_outline:
# Turn off
if self.__class__._handle is not None:
bpy.types.SpaceView3D.draw_handler_remove(self.__class__._handle, 'WINDOW')
self.__class__._handle = None
wm.draw_selection_outline = False
self.report({'INFO'}, "Selection outline stopped")
return {'FINISHED'}
else:
# Turn on
if self.__class__._handle is None:
self.__class__._handle = bpy.types.SpaceView3D.draw_handler_add(
draw_callback, (), 'WINDOW', 'POST_VIEW'
)
wm.draw_selection_outline = True
context.window_manager.modal_handler_add(self)
self.report({'INFO'}, "Selection outline started")
return {'RUNNING_MODAL'}
def modal(self, context, event):
if context.area:
context.area.tag_redraw()
if not context.window_manager.draw_selection_outline:
return {'CANCELLED'}
return {'PASS_THROUGH'}
class VIEW3D_PT_selection_outline(bpy.types.Panel):
bl_label = "Selection Outline"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Outline"
def draw(self, context):
layout = self.layout
wm = context.window_manager
row = layout.row()
if wm.draw_selection_outline:
row.operator("view3d.draw_selection_outline", text="Disable Outline", icon='CANCEL')
else:
row.operator("view3d.draw_selection_outline", text="Enable Outline", icon='RESTRICT_VIEW_OFF')
layout.prop(wm, "outline_thickness")
layout.prop(wm, "outline_color", text="Object Color")
@persistent
def enable_outline_on_load(dummy):
"""Automatically enable outline drawing when Blender starts or loads a file"""
# Use a timer to ensure everything is properly initialized
bpy.app.timers.register(lambda: start_outline_drawing(), first_interval=0.1)
def start_outline_drawing():
"""Start the outline drawing"""
wm = bpy.context.window_manager
if not wm.draw_selection_outline:
# Enable the outline
if VIEW3D_OT_draw_selection_outline._handle is None:
VIEW3D_OT_draw_selection_outline._handle = bpy.types.SpaceView3D.draw_handler_add(
draw_callback, (), 'WINDOW', 'POST_VIEW'
)
wm.draw_selection_outline = True
print("Selection outline auto-enabled")
return None # Don't repeat the timer
def register():
bpy.utils.register_class(VIEW3D_OT_draw_selection_outline)
bpy.utils.register_class(VIEW3D_PT_selection_outline)
bpy.types.WindowManager.draw_selection_outline = bpy.props.BoolProperty(default=False)
bpy.types.WindowManager.outline_thickness = bpy.props.FloatProperty(
name="Line Thickness", default=6.0, min=1.0, max=100.0
)
bpy.types.WindowManager.outline_color = bpy.props.FloatVectorProperty(
name="Object Color", subtype='COLOR', size=4,
min=0.0, max=1.0, default=(0.04, 1.0, 0, 1.0)
)
# Add the handler to auto-enable on startup
bpy.app.handlers.load_post.append(enable_outline_on_load)
# Also enable it immediately when the addon is first registered
if bpy.context.window_manager is not None:
bpy.app.timers.register(lambda: start_outline_drawing(), first_interval=0.1)
def unregister():
# Remove the load handler
if enable_outline_on_load in bpy.app.handlers.load_post:
bpy.app.handlers.load_post.remove(enable_outline_on_load)
# Stop the drawing if active
if VIEW3D_OT_draw_selection_outline._handle is not None:
bpy.types.SpaceView3D.draw_handler_remove(VIEW3D_OT_draw_selection_outline._handle, 'WINDOW')
VIEW3D_OT_draw_selection_outline._handle = None
bpy.utils.unregister_class(VIEW3D_OT_draw_selection_outline)
bpy.utils.unregister_class(VIEW3D_PT_selection_outline)
del bpy.types.WindowManager.draw_selection_outline
del bpy.types.WindowManager.outline_thickness
del bpy.types.WindowManager.outline_color
if __name__ == "__main__":
register()