Rework/New: Object & Mesh Select Pies #21
@ -2,8 +2,12 @@
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from bpy.types import Menu
|
||||
import bpy, re
|
||||
from bpy.types import Menu, Operator, Constraint, UILayout, Object
|
||||
from .hotkeys import register_hotkey
|
||||
from bpy.props import BoolProperty, StringProperty
|
||||
from bpy.utils import flip_name
|
||||
from .pie_camera import get_current_camera
|
||||
|
||||
|
||||
class PIE_MT_object_selection(Menu):
|
||||
@ -14,25 +18,517 @@ class PIE_MT_object_selection(Menu):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
# 4 - LEFT
|
||||
pie.operator("object.select_grouped", text="Select Grouped")
|
||||
pie.operator("object.select_parent_object", text="Parent", icon='FILE_PARENT')
|
||||
# 6 - RIGHT
|
||||
pie.operator("object.select_by_type", text="Select By Type")
|
||||
pie.operator("object.select_children_of_active", text=f"Direct Children", icon='CON_CHILDOF')
|
||||
# 2 - BOTTOM
|
||||
pie.operator(
|
||||
"object.select_all", text="Invert Selection", icon='ZOOM_PREVIOUS'
|
||||
).action = 'INVERT'
|
||||
pie.operator("object.select_all", text="Deselect All", icon='OUTLINER_DATA_POINTCLOUD').action='DESELECT'
|
||||
# 8 - TOP
|
||||
pie.operator(
|
||||
"object.select_all", text="Select All Toggle", icon='NONE'
|
||||
).action = 'TOGGLE'
|
||||
pie.operator("object.select_all", text="Select All", icon='OUTLINER_OB_POINTCLOUD').action='SELECT'
|
||||
# 7 - TOP - LEFT
|
||||
pie.operator("view3d.select_circle", text="Circle Select")
|
||||
pie.operator("object.select_all", text="Invert Selection", icon='CLIPUV_DEHLT').action='INVERT'
|
||||
# 9 - TOP - RIGHT
|
||||
pie.operator("view3d.select_box", text="Box Select")
|
||||
pie.operator("object.select_siblings_of_active", text="Siblings", icon='PARTICLES')
|
||||
# 1 - BOTTOM - LEFT
|
||||
pie.operator("object.select_camera", text="Select Camera")
|
||||
text = "Active Camera"
|
||||
cam = get_current_camera(context)
|
||||
if cam:
|
||||
text += f" ({cam.name})"
|
||||
pie.operator("object.select_active_camera", text=text, icon='OUTLINER_OB_CAMERA')
|
||||
# 3 - BOTTOM - RIGHT
|
||||
pie.menu("PIE_MT_object_selection_more", text="Select Menu")
|
||||
pie.menu("PIE_MT_object_selection_more", text="Select Menu", icon='THREE_DOTS')
|
||||
|
||||
|
||||
class PIE_MT_object_selection_more(Menu):
|
||||
bl_idname = "PIE_MT_object_selection_more"
|
||||
bl_label = "More Object Select"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
# Modal selection operators.
|
||||
layout.separator()
|
||||
layout.operator("view3d.select_circle", text="Circle Select", icon='MESH_CIRCLE')
|
||||
layout.operator("view3d.select_box", text="Box Select", icon='SELECT_SET')
|
||||
layout.operator_menu_enum("view3d.select_lasso", "mode", icon='MOD_CURVE')
|
||||
|
||||
# Select based on the active object.
|
||||
layout.separator()
|
||||
layout.operator_menu_enum("object.select_grouped", "type", text="Select Grouped", icon='OUTLINER_COLLECTION')
|
||||
layout.operator_menu_enum("object.select_linked", "type", text="Select Linked", icon='DUPLICATE')
|
||||
|
||||
# Select based on parameters.
|
||||
layout.separator()
|
||||
layout.operator_menu_enum("object.select_by_type", "type", text="Select All by Type", icon='OUTLINER_OB_MESH')
|
||||
layout.operator("object.select_random", text="Select Random", icon='RNDCURVE')
|
||||
layout.operator("object.select_pattern", text="Select Pattern...", icon='FILTER')
|
||||
layout.operator('object.select_by_name_search', icon='VIEWZOOM')
|
||||
|
||||
layout.separator()
|
||||
layout.menu("VIEW3D_MT_select_any_camera", text="Select Camera", icon='OUTLINER_OB_CAMERA')
|
||||
|
||||
|
||||
class VIEW3D_MT_select_any_camera(Menu):
|
||||
bl_idname = "VIEW3D_MT_select_any_camera"
|
||||
bl_label = "Select Camera"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
active_cam = get_current_camera(context)
|
||||
|
||||
all_cams = [obj for obj in sorted(context.scene.objects, key=lambda o: o.name) if obj.type == 'CAMERA']
|
||||
|
||||
for cam in all_cams:
|
||||
icon = 'OUTLINER_DATA_CAMERA'
|
||||
if cam == active_cam:
|
||||
icon = 'OUTLINER_OB_CAMERA'
|
||||
layout.operator('object.select_object_by_name', text=cam.name, icon=icon).obj_name=cam.name
|
||||
|
||||
|
||||
### Object relationship selection operators.
|
||||
|
||||
|
||||
def get_selected_parents(context) -> list:
|
||||
"""Return objects which are selected or active, and have children.
|
||||
Active object is always first."""
|
||||
objects = context.selected_objects or [context.active_object]
|
||||
parents = [o for o in objects if o.children]
|
||||
if context.active_object and context.active_object in parents:
|
||||
idx = parents.index(context.active_object)
|
||||
if idx > -1:
|
||||
parents.pop(idx)
|
||||
parents.insert(0, context.active_object)
|
||||
return parents
|
||||
|
||||
|
||||
def deselect_all_objects(context):
|
||||
for obj in context.selected_objects:
|
||||
obj.select_set(False)
|
||||
|
||||
|
||||
class ObjectSelectOperatorMixin:
|
||||
extend_selection: BoolProperty(
|
||||
name="Extend Selection",
|
||||
description="Objects that are already selected will remain selected",
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
if event.shift:
|
||||
self.extend_selection = True
|
||||
else:
|
||||
self.extend_selection = False
|
||||
|
||||
return self.execute(context)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.mode != 'OBJECT':
|
||||
cls.poll_message_set("Must be in Object Mode.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
if not self.extend_selection:
|
||||
deselect_all_objects(context)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_select_children_of_active(Operator, ObjectSelectOperatorMixin):
|
||||
"""Select the children of the active object"""
|
||||
|
||||
bl_idname = "object.select_children_of_active"
|
||||
bl_label = "Select Children"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
recursive: BoolProperty(default=False, options={'SKIP_SAVE'})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not super().poll(context):
|
||||
return False
|
||||
obj = context.active_object
|
||||
if not (obj and obj.children):
|
||||
cls.poll_message_set("Active object has no children.")
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, props):
|
||||
recursively = " recursively" if props.recursive else ""
|
||||
return f"Select children of active object{recursively}." + "\n\nShift: Extend current selection"
|
||||
|
||||
def execute(self, context):
|
||||
super().execute(context)
|
||||
|
||||
counter = 0
|
||||
children = context.active_object.children
|
||||
if self.recursive:
|
||||
children = context.active_object.children_recursive
|
||||
for child in children:
|
||||
if not child.select_get():
|
||||
counter += 1
|
||||
child.select_set(True)
|
||||
|
||||
self.report({'INFO'}, f"Selected {counter} objects.")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_select_siblings_of_active(Operator, ObjectSelectOperatorMixin):
|
||||
"""Select siblings of active object.\n\nShift: Extend current selection"""
|
||||
|
||||
bl_idname = "object.select_siblings_of_active"
|
||||
bl_label = "Select Siblings"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not super().poll(context):
|
||||
return False
|
||||
obj = context.active_object
|
||||
if not (obj and obj.parent):
|
||||
cls.poll_message_set("Active object has no parent.")
|
||||
return False
|
||||
if not len(obj.parent.children) > 1:
|
||||
cls.poll_message_set("Active object has no siblings.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
super().execute(context)
|
||||
|
||||
counter = 0
|
||||
for child in context.active_object.parent.children:
|
||||
if not child.select_get():
|
||||
counter += 1
|
||||
child.select_set(True)
|
||||
|
||||
self.report({'INFO'}, f"Selected {counter} objects.")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_select_parent_object(Operator, ObjectSelectOperatorMixin):
|
||||
"""Select parent of the active object.\n\nShift: Extend current selection"""
|
||||
|
||||
bl_idname = "object.select_parent_object"
|
||||
bl_label = "Select Parent Object"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not super().poll(context):
|
||||
return False
|
||||
obj = context.active_object
|
||||
if not (obj and obj.parent):
|
||||
cls.poll_message_set("Active object has no parent.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
super().execute(context)
|
||||
|
||||
active_obj = context.active_object
|
||||
|
||||
active_obj.parent.select_set(True)
|
||||
context.view_layer.objects.active = active_obj.parent
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_select_active_camera(Operator, ObjectSelectOperatorMixin):
|
||||
"""Select the active camera of the scene or viewport"""
|
||||
|
||||
bl_idname = "object.select_active_camera"
|
||||
bl_label = "Select Active Camera"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
cam = get_current_camera(context)
|
||||
if not cam:
|
||||
cls.poll_message_set("No active camera.")
|
||||
return False
|
||||
if not cam.visible_get():
|
||||
cls.poll_message_set("Camera is hidden, it cannot be selected.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
super().execute(context)
|
||||
cam = get_current_camera(context)
|
||||
if context.active_object and context.mode != 'MODE':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
cam.select_set(True)
|
||||
context.view_layer.objects.active = cam
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
### Object name-based selection operators.
|
||||
|
||||
|
||||
class PIE_MT_select_object_name_relation(Menu):
|
||||
bl_idname = 'PIE_MT_select_object_name_relation'
|
||||
bl_label = "Select by Name"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
active_obj = context.active_object
|
||||
|
||||
pie = layout.menu_pie()
|
||||
|
||||
# 4 - LEFT
|
||||
pie.operator(
|
||||
OBJECT_OT_select_symmetry_object.bl_idname,
|
||||
text="Flip Selection",
|
||||
icon='MOD_MIRROR',
|
||||
)
|
||||
# 6 - RIGHT
|
||||
pie.operator('object.select_by_name_search', icon='VIEWZOOM')
|
||||
# 2 - BOTTOM
|
||||
lower_obj = context.scene.objects.get(increment_name(active_obj.name, increment=-1))
|
||||
if lower_obj:
|
||||
op = pie.operator('object.select_object_by_name', text=lower_obj.name, icon='TRIA_DOWN')
|
||||
op.obj_name = lower_obj.name
|
||||
else:
|
||||
pie.separator()
|
||||
# 8 - TOP
|
||||
higher_obj = context.scene.objects.get(increment_name(active_obj.name, increment=1)) or context.scene.objects.get(active_obj.name + ".001")
|
||||
if higher_obj:
|
||||
op = pie.operator('object.select_object_by_name', text=higher_obj.name, icon='TRIA_UP')
|
||||
op.obj_name = higher_obj.name
|
||||
else:
|
||||
pie.separator()
|
||||
# 7 - TOP - LEFT
|
||||
constraints = OBJECT_MT_PIE_constrained_objects.get_dependent_constraints(context)
|
||||
if len(constraints) == 1:
|
||||
draw_select_constraint_owner(pie, constraints[0])
|
||||
elif len(constraints) > 1:
|
||||
pie.menu('OBJECT_MT_PIE_constrained_objects', icon='THREE_DOTS')
|
||||
else:
|
||||
pie.separator()
|
||||
# 9 - TOP - RIGHT
|
||||
constraints = OBJECT_MT_PIE_obj_constraint_targets.get_constraints_with_target(context)
|
||||
if len(constraints) == 1:
|
||||
draw_select_constraint_target(pie, constraints[0])
|
||||
elif len(constraints) > 1:
|
||||
pie.menu('OBJECT_MT_PIE_obj_constraint_targets', icon='THREE_DOTS')
|
||||
else:
|
||||
pie.separator()
|
||||
# 1 - BOTTOM - LEFT
|
||||
pie.operator("object.select_pattern", text=f"Select Pattern...", icon='FILTER')
|
||||
# 3 - BOTTOM - RIGHT
|
||||
pie.separator()
|
||||
|
||||
|
||||
class OBJECT_OT_select_symmetry_object(Operator, ObjectSelectOperatorMixin):
|
||||
"""Select opposite objects.\n\nShift: Extend current selection"""
|
||||
|
||||
bl_idname = "object.select_opposite"
|
||||
bl_label = "Select Opposite Object"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
scene_obs = context.scene.objects
|
||||
sel_obs = context.selected_objects[:]
|
||||
active_obj = context.active_object
|
||||
flipped_objs = [scene_obs.get(flip_name(ob.name)) for ob in sel_obs]
|
||||
flipped_active = scene_obs.get(flip_name(active_obj.name))
|
||||
if not (flipped_active or any(flipped_objs)):
|
||||
cls.poll_message_set("No selected objects with corresponding opposite objects.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
scene_obs = context.scene.objects
|
||||
sel_obs = context.selected_objects[:]
|
||||
active_obj = context.active_object
|
||||
|
||||
flipped_objs = [scene_obs.get(flip_name(ob.name)) for ob in sel_obs]
|
||||
flipped_active = scene_obs.get(flip_name(active_obj.name))
|
||||
|
||||
notflipped = sum([flipped_obj in (obj, None) for obj, flipped_obj in zip(sel_obs, flipped_objs)])
|
||||
if notflipped > 0:
|
||||
self.report({'WARNING'}, f"{notflipped} objects had no opposite.")
|
||||
|
||||
super().execute(context)
|
||||
|
||||
for ob in flipped_objs:
|
||||
if not ob:
|
||||
continue
|
||||
ob.select_set(True)
|
||||
|
||||
if flipped_active:
|
||||
context.view_layer.objects.active = flipped_active
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_select_object_by_name_search(Operator, ObjectSelectOperatorMixin):
|
||||
"""Select an object via a search box"""
|
||||
|
||||
bl_idname = "object.select_by_name_search"
|
||||
bl_label = "Search Object..."
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
obj_name: StringProperty(name="Object")
|
||||
|
||||
def invoke(self, context, _event):
|
||||
obj = context.active_object
|
||||
if obj:
|
||||
self.obj_name = obj.name
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
layout.prop_search(
|
||||
self, 'obj_name', context.scene, 'objects', icon='OBJECT_DATA'
|
||||
)
|
||||
layout.prop(self, 'extend_selection')
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.scene.objects.get(self.obj_name)
|
||||
if not self.extend_selection:
|
||||
deselect_all_objects(context)
|
||||
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = obj
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_select_object_by_name(Operator, ObjectSelectOperatorMixin):
|
||||
"""Select this object.\n\nShift: Extend current selection"""
|
||||
|
||||
bl_idname = "object.select_object_by_name"
|
||||
bl_label = "Select Object By Name"
|
||||
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
||||
|
||||
obj_name: StringProperty(
|
||||
name="Object Name", description="Name of the object to select"
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.scene.objects.get(self.obj_name)
|
||||
|
||||
if not obj:
|
||||
self.report({'ERROR'}, "Object name not found in scene: " + self.obj_name)
|
||||
return {'CANCELLED'}
|
||||
|
||||
if not obj.visible_get():
|
||||
self.report({'ERROR'}, "Object is hidden, so it cannot be selected.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
super().execute(context)
|
||||
|
||||
obj.select_set(True)
|
||||
context.view_layer.objects.active = obj
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def increment_name(name: str, increment=1, default_zfill=1) -> str:
|
||||
# Increment LAST number in the name.
|
||||
# Negative numbers will be clamped to 0.
|
||||
# Digit length will be preserved, so 10 will decrement to 09.
|
||||
# 99 will increment to 100, not 00.
|
||||
|
||||
# If no number was found, one will be added at the end of the base name.
|
||||
# The length of this in digits is set with the `default_zfill` param.
|
||||
|
||||
# This is not meant to be able to add a whole .001 suffix at the end of a name,
|
||||
# although we can account for the inverse of that easily enough.
|
||||
|
||||
if name.endswith(".001") and increment==-1:
|
||||
# Special case.
|
||||
return name[:-4]
|
||||
|
||||
numbers_in_name = re.findall(r'\d+', name)
|
||||
if not numbers_in_name:
|
||||
return name + "_" + str(max(0, increment)).zfill(default_zfill)
|
||||
|
||||
last = numbers_in_name[-1]
|
||||
incremented = str(max(0, int(last) + increment)).zfill(len(last))
|
||||
split = name.rsplit(last, 1)
|
||||
return incremented.join(split)
|
||||
|
||||
|
||||
class OBJECT_MT_PIE_obj_constraint_targets(Menu):
|
||||
bl_label = "Constraint Targets"
|
||||
|
||||
@staticmethod
|
||||
def get_constraints_with_target(context):
|
||||
return [con for con in context.active_object.constraints if hasattr(con, 'target') and con.target in set(context.scene.objects)]
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return cls.get_constraints_with_target(context)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
for constraint in self.get_constraint_targets(context):
|
||||
draw_select_constraint_target(layout, constraint)
|
||||
|
||||
|
||||
class OBJECT_MT_PIE_constrained_objects(Menu):
|
||||
bl_label = "Constrained Objects"
|
||||
|
||||
@staticmethod
|
||||
def get_dependent_constraints(context):
|
||||
dependent_ids = bpy.data.user_map()[context.active_object]
|
||||
dependent_objects = [id for id in dependent_ids if type(id)==Object and id in set(context.scene.objects)]
|
||||
ret = []
|
||||
for dependent_object in dependent_objects:
|
||||
for con in dependent_object.constraints:
|
||||
if hasattr(con, 'target') and con.target == context.active_object:
|
||||
ret.append(con)
|
||||
break
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return cls.get_dependent_constraints(context)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
for constraint in self.get_dependent_constraints(context):
|
||||
draw_select_constraint_owner(layout, constraint)
|
||||
|
||||
|
||||
def draw_select_constraint_target(layout, constraint):
|
||||
icon = get_constraint_icon(constraint)
|
||||
layout.operator(
|
||||
'object.select_object_by_name',
|
||||
text=f"Constraint Target: {constraint.target.name} ({constraint.name})",
|
||||
icon=icon,
|
||||
).obj_name=constraint.target.name
|
||||
|
||||
|
||||
def draw_select_constraint_owner(layout, constraint):
|
||||
icon = get_constraint_icon(constraint)
|
||||
layout.operator(
|
||||
'object.select_object_by_name',
|
||||
text=f"Constrained Object: {constraint.id_data.name} ({constraint.name})",
|
||||
icon=icon,
|
||||
).obj_name=constraint.id_data.name
|
||||
|
||||
|
||||
def get_constraint_icon(constraint: Constraint) -> str:
|
||||
"""We do not ask questions about this function. We accept it."""
|
||||
if constraint.type == 'ACTION':
|
||||
return 'ACTION'
|
||||
|
||||
icons = UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys()
|
||||
# This magic number can change between blender versions. Last updated: 4.1.1
|
||||
constraint_icon_magic_offset = 42
|
||||
return icons[UILayout.icon(constraint) - constraint_icon_magic_offset]
|
||||
|
||||
### Mesh selection UI.
|
||||
|
||||
|
||||
class PIE_MT_mesh_selection(Menu):
|
||||
@ -43,89 +539,125 @@ class PIE_MT_mesh_selection(Menu):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
# 4 - LEFT
|
||||
pie.operator("mesh.select_less", text="Select Less")
|
||||
pie.operator("mesh.select_less", text="Select Less", icon='REMOVE')
|
||||
# 6 - RIGHT
|
||||
pie.operator("mesh.select_more", text="Select More")
|
||||
pie.operator("mesh.select_more", text="Select More", icon='ADD')
|
||||
# 2 - BOTTOM
|
||||
pie.menu("VIEW3D_MT_edit_mesh_select_loops")
|
||||
pie.operator("mesh.select_all", text="Deselect All", icon='OUTLINER_DATA_POINTCLOUD').action='DESELECT'
|
||||
# 8 - TOP
|
||||
pie.operator("mesh.select_all", text="Select All Toggle").action = 'TOGGLE'
|
||||
pie.operator("mesh.select_all", text="Select All", icon='OUTLINER_OB_POINTCLOUD').action='SELECT'
|
||||
# 7 - TOP - LEFT
|
||||
pie.operator("view3d.select_circle", text="Circle Select")
|
||||
pie.operator("mesh.select_all", text="Invert Selection", icon='CLIPUV_DEHLT').action='INVERT'
|
||||
# 9 - TOP - RIGHT
|
||||
pie.operator("view3d.select_box", text="Box Select")
|
||||
pie.operator("mesh.select_linked", text="Select Linked", icon='FILE_3D')
|
||||
# 1 - BOTTOM - LEFT
|
||||
pie.operator("mesh.select_all", text="Invert Selection").action = 'INVERT'
|
||||
pie.operator('wm.call_menu_pie', text='Select Loops...', icon='MOD_WAVE').name='PIE_MT_mesh_selection_loops'
|
||||
# 3 - BOTTOM - RIGHT
|
||||
pie.menu("PIE_MT_mesh_selection_mode", text="Edit Modes", icon='VERTEXSEL')
|
||||
pie.menu("PIE_MT_mesh_selection_more", text="More...", icon='THREE_DOTS')
|
||||
|
||||
|
||||
class PIE_MT_object_selection_more(Menu):
|
||||
bl_idname = "PIE_MT_object_selection_more"
|
||||
bl_label = "More Object Select"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
box = pie.split().column()
|
||||
box.operator("object.select_random", text="Select Random")
|
||||
box.operator("object.select_linked", text="Select Linked")
|
||||
box.separator()
|
||||
|
||||
box.operator("object.select_more", text="More")
|
||||
box.operator("object.select_less", text="Less")
|
||||
box.separator()
|
||||
|
||||
props = box.operator("object.select_hierarchy", text="Parent")
|
||||
props.extend = False
|
||||
props.direction = 'PARENT'
|
||||
|
||||
props = box.operator("object.select_hierarchy", text="Child")
|
||||
props.extend = False
|
||||
props.direction = 'CHILD'
|
||||
box.separator()
|
||||
|
||||
props = box.operator("object.select_hierarchy", text="Extend Parent")
|
||||
props.extend = True
|
||||
props.direction = 'PARENT'
|
||||
|
||||
props = box.operator("object.select_hierarchy", text="Extend Child")
|
||||
props.extend = True
|
||||
props.direction = 'CHILD'
|
||||
|
||||
|
||||
class PIE_MT_mesh_selection_mode(Menu):
|
||||
bl_idname = "PIE_MT_mesh_selection_mode"
|
||||
class PIE_MT_mesh_selection_more(Menu):
|
||||
bl_idname = "PIE_MT_mesh_selection_more"
|
||||
bl_label = "Mesh Selection Mode"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
pie = layout.menu_pie()
|
||||
box = pie.split().column()
|
||||
|
||||
box.operator("mesh.select_mode", text="Vertex", icon='VERTEXSEL').type = 'VERT'
|
||||
box.operator("mesh.select_mode", text="Edge", icon='EDGESEL').type = 'EDGE'
|
||||
box.operator("mesh.select_mode", text="Face", icon='FACESEL').type = 'FACE'
|
||||
vert_mode, edge_mode, face_mode = context.tool_settings.mesh_select_mode
|
||||
|
||||
# Selection modes.
|
||||
layout.operator("mesh.select_mode", text="Vertex Select Mode", icon='VERTEXSEL', depress=vert_mode).type = 'VERT'
|
||||
layout.operator("mesh.select_mode", text="Edge Select Mode", icon='EDGESEL', depress=edge_mode).type = 'EDGE'
|
||||
layout.operator("mesh.select_mode", text="Face Select Mode", icon='FACESEL', depress=face_mode).type = 'FACE'
|
||||
|
||||
# Modal selection operators.
|
||||
layout.separator()
|
||||
layout.operator("view3d.select_circle", text="Circle Select", icon='MESH_CIRCLE')
|
||||
layout.operator("view3d.select_box", text="Box Select", icon='SELECT_SET')
|
||||
layout.operator_menu_enum("view3d.select_lasso", "mode", icon='MOD_CURVE')
|
||||
|
||||
# Select based on what's active.
|
||||
layout.separator()
|
||||
layout.menu("VIEW3D_MT_edit_mesh_select_linked", icon='FILE_3D')
|
||||
layout.menu("VIEW3D_MT_edit_mesh_select_similar", icon='CON_TRANSLIKE')
|
||||
layout.operator_menu_enum("mesh.select_axis", "axis", text="Select Side", icon='AXIS_SIDE')
|
||||
layout.operator("mesh.select_nth", text="Checker Deselect", icon='TEXTURE')
|
||||
|
||||
# Select based on some parameter.
|
||||
layout.separator()
|
||||
layout.menu("VIEW3D_MT_edit_mesh_select_by_trait")
|
||||
layout.operator("mesh.select_by_attribute", text="Select All By Attribute", icon='GEOMETRY_NODES')
|
||||
|
||||
# User-defined GeoNode Select operations.
|
||||
layout.template_node_operator_asset_menu_items(catalog_path="Select")
|
||||
|
||||
|
||||
class PIE_MT_mesh_selection_loops(Menu):
|
||||
bl_idname = "PIE_MT_mesh_selection_loops"
|
||||
bl_label = "Select Loop"
|
||||
|
||||
def draw(self, context):
|
||||
pie = self.layout.menu_pie()
|
||||
|
||||
# 4 - LEFT
|
||||
pie.operator('mesh.loop_multi_select', text="Edge Loops", icon='MOD_WAVE').ring=False
|
||||
# 6 - RIGHT
|
||||
pie.operator('mesh.loop_multi_select', text="Edge Rings", icon='CURVES').ring=True
|
||||
# 2 - BOTTOM
|
||||
pie.operator('mesh.loop_to_region', text="Loop Inner Region", icon='SHADING_SOLID')
|
||||
# 8 - TOP
|
||||
pie.operator('mesh.region_to_loop', text="Boundary Loop", icon='MESH_CIRCLE')
|
||||
|
||||
|
||||
registry = [
|
||||
PIE_MT_object_selection,
|
||||
PIE_MT_mesh_selection,
|
||||
PIE_MT_object_selection_more,
|
||||
PIE_MT_mesh_selection_mode,
|
||||
VIEW3D_MT_select_any_camera,
|
||||
|
||||
OBJECT_OT_select_children_of_active,
|
||||
OBJECT_OT_select_siblings_of_active,
|
||||
OBJECT_OT_select_parent_object,
|
||||
|
||||
OBJECT_OT_select_active_camera,
|
||||
|
||||
PIE_MT_select_object_name_relation,
|
||||
OBJECT_OT_select_symmetry_object,
|
||||
OBJECT_OT_select_object_by_name_search,
|
||||
OBJECT_OT_select_object_by_name,
|
||||
|
||||
PIE_MT_mesh_selection,
|
||||
PIE_MT_mesh_selection_more,
|
||||
PIE_MT_mesh_selection_loops,
|
||||
]
|
||||
|
||||
|
||||
def draw_edge_sharpness_in_traits_menu(self, context):
|
||||
self.layout.operator("mesh.edges_select_sharp", text="Edge Sharpness")
|
||||
|
||||
|
||||
def register():
|
||||
# Weird that this built-in operator is not included in this built-in menu.
|
||||
bpy.types.VIEW3D_MT_edit_mesh_select_by_trait.prepend(draw_edge_sharpness_in_traits_menu)
|
||||
|
||||
register_hotkey(
|
||||
'wm.call_menu_pie',
|
||||
op_kwargs={'name': 'PIE_MT_object_selection'},
|
||||
hotkey_kwargs={'type': "A", 'value': "PRESS"},
|
||||
key_cat="Object Mode",
|
||||
)
|
||||
register_hotkey(
|
||||
'wm.call_menu_pie',
|
||||
op_kwargs={'name': 'PIE_MT_select_object_name_relation'},
|
||||
hotkey_kwargs={'type': "F", 'value': "PRESS", 'ctrl': True},
|
||||
key_cat="Object Mode",
|
||||
)
|
||||
register_hotkey(
|
||||
'wm.call_menu_pie',
|
||||
op_kwargs={'name': 'PIE_MT_mesh_selection'},
|
||||
hotkey_kwargs={'type': "A", 'value': "PRESS"},
|
||||
key_cat="Mesh",
|
||||
)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.VIEW3D_MT_edit_mesh_select_by_trait.remove(draw_edge_sharpness_in_traits_menu)
|
||||
|
Loading…
Reference in New Issue
Block a user