[feat] add more virtools group related operators

- support select/filter by virtools group.
- support group/ungroup/clear in object context menu.
- improve virtools group visit functions
This commit is contained in:
yyc12345 2022-11-19 14:30:31 +08:00
parent 240d5612df
commit 02c11ffe5a
8 changed files with 364 additions and 44 deletions

View File

@ -332,9 +332,9 @@ def import_bm(context, bmx_filepath, prefs_fncg, prefs_externalTexture, prefs_te
# write custom property # write custom property
if len(object_groupList) != 0: if len(object_groupList) != 0:
UTILS_virtools_prop.set_virtools_group_data(object_target, tuple(object_groupList)) UTILS_virtools_prop.fill_virtools_group_data(object_target, tuple(object_groupList))
else: else:
UTILS_virtools_prop.set_virtools_group_data(object_target, None) UTILS_virtools_prop.fill_virtools_group_data(object_target, None)
# update view layer after all objects has been imported # update view layer after all objects has been imported
blender_viewLayer.update() blender_viewLayer.update()

View File

@ -2,7 +2,7 @@ import bpy, mathutils
from . import UTILS_functions from . import UTILS_functions
class BALLANCE_OT_super_align(bpy.types.Operator): class BALLANCE_OT_super_align(bpy.types.Operator):
"""Align object with 3ds Max way""" """Align object with 3ds Max style"""
bl_idname = "ballance.super_align" bl_idname = "ballance.super_align"
bl_label = "3ds Max Align" bl_label = "3ds Max Align"
bl_options = {'UNDO'} bl_options = {'UNDO'}
@ -12,7 +12,7 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
align_z: bpy.props.BoolProperty(name="Z position") align_z: bpy.props.BoolProperty(name="Z position")
current_references: bpy.props.EnumProperty( current_references: bpy.props.EnumProperty(
name="Reference", name="Reference (Active Object)",
items=(('MIN', "Min", ""), items=(('MIN', "Min", ""),
('CENTER', "Center (bound box)", ""), ('CENTER', "Center (bound box)", ""),
('POINT', "Center (axis)", ""), ('POINT', "Center (axis)", ""),
@ -21,7 +21,7 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
) )
target_references: bpy.props.EnumProperty( target_references: bpy.props.EnumProperty(
name="Target", name="Target (Other Objects)",
items=(('MIN', "Min", ""), items=(('MIN', "Min", ""),
('CENTER', "Center (bound box)", ""), ('CENTER', "Center (bound box)", ""),
('POINT', "Center (axis)", ""), ('POINT', "Center (axis)", ""),

View File

@ -436,7 +436,7 @@ def _set_for_group(obj, name_info):
# apply to custom property # apply to custom property
UTILS_virtools_prop.set_virtools_group_data(obj, tuple(gps)) UTILS_virtools_prop.fill_virtools_group_data(obj, tuple(gps))
# ========================================== # ==========================================
# assemble funcs # assemble funcs

View File

@ -0,0 +1,221 @@
import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
class common_group_name_props(bpy.types.Operator):
use_custom_name: bpy.props.BoolProperty(
name="Use Custom Name",
description="Whether use user defined group name.",
default=False,
)
group_name: bpy.props.EnumProperty(
name="Group Name",
description="Pick vanilla Ballance group name.",
items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
)
custom_group_name: bpy.props.StringProperty(
name="Custom Group Name",
description="Input your custom group name.",
default="",
)
def get_group_name_string(self):
return str(self.custom_group_name if self.use_custom_name else self.group_name)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
class BALLANCE_OT_select_virtools_group(common_group_name_props):
"""Select objects by Virtools Group."""
bl_idname = "ballance.select_virtools_group"
bl_label = "Select by Virtools Group"
bl_options = {'UNDO'}
merge_selection: bpy.props.BoolProperty(
name="Merge Selection",
description="Merge selection, rather than re-select them.",
default=False,
)
ignore_hide: bpy.props.BoolProperty(
name="Ignore Hide Property",
description="Select objects without considering visibility.",
default=False,
)
def execute(self, context):
# iterate object
for obj in bpy.context.scene.objects:
# ignore hidden objects
if (not self.ignore_hide) and obj.hide_get() == True:
continue
# check group
if UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()):
# select object
obj.select_set(True)
else:
# if not in merge mode, deselect them
if not self.merge_selection:
obj.select_set(False)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'ignore_hide')
row.prop(self, 'merge_selection')
layout.separator()
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_filter_virtools_group(common_group_name_props):
"""Filter objects by Virtools Group."""
bl_idname = "ballance.filter_virtools_group"
bl_label = "Filter by Virtools Group"
bl_options = {'UNDO'}
reverse_selection: bpy.props.BoolProperty(
name="Reverse",
description="Reverse operation. Remove matched objects.",
default=False,
)
ignore_hide: bpy.props.BoolProperty(
name="Ignore Hide Property",
description="Select objects without considering visibility.",
default=False,
)
def execute(self, context):
# make a copy for all objects, to ensure it is not viotile
# becuase we need deselect some objects in for statement
selected = bpy.context.selected_objects[:]
# iterate object
for obj in selected:
# ignore hidden objects
if (not self.ignore_hide) and obj.hide_get() == True:
continue
# check group and decide select
is_selected = UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string())
if self.reverse_selection:
is_selected = not is_selected
# select object
obj.select_set(is_selected)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'ignore_hide')
row.prop(self, 'reverse_selection')
layout.separator()
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_set_group(common_group_name_props):
"""Grouping selected objects"""
bl_idname = "ballance.ctx_set_group"
bl_label = "Grouping Objects"
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def execute(self, context):
has_duplicated = False
# iterate object
for obj in bpy.context.selected_objects:
# try setting
if not UTILS_virtools_prop.add_virtools_group_data(obj, self.get_group_name_string()):
has_duplicated = True
# throw a warning if some objects have duplicated group
if has_duplicated:
UTILS_functions.show_message_box(("Some objects have duplicated group name.", "These objects have been omitted.", ), "Duplicated Group", 'ERROR')
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_unset_group(common_group_name_props):
"""Ungrouping selected objects"""
bl_idname = "ballance.ctx_unset_group"
bl_label = "Ungrouping Objects"
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def execute(self, context):
lack_group = False
# iterate object
for obj in bpy.context.selected_objects:
# try unsetting
if not UTILS_virtools_prop.remove_virtools_group_data(obj, self.get_group_name_string()):
lack_group = True
# throw a warning if some objects have duplicated group
if lack_group:
UTILS_functions.show_message_box(("Some objects lack specified group name.", "These objects have been omitted.", ), "Lack Group", 'ERROR')
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_clear_group(bpy.types.Operator):
"""Clear Virtools Groups for selected objects"""
bl_idname = "ballance.ctx_clear_group"
bl_label = "Clear Grouping"
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def execute(self, context):
# iterate object
for obj in bpy.context.selected_objects:
UTILS_virtools_prop.clear_virtools_group_data(obj)
return {'FINISHED'}

View File

@ -7,31 +7,50 @@ class BALLANCE_OT_add_virtools_group(bpy.types.Operator):
bl_label = "Add Virtools Group" bl_label = "Add Virtools Group"
bl_options = {'UNDO'} bl_options = {'UNDO'}
use_custom_name: bpy.props.BoolProperty(
name="Use Custom Name",
description="Whether use user defined group name.",
default=False,
)
group_name: bpy.props.EnumProperty( group_name: bpy.props.EnumProperty(
name="Group Name", name="Group Name",
description="Group name. For custom group name, please pick `CustomCKGroup` and change it later.", description="Pick vanilla Ballance group name.",
items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups), items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
) )
custom_group_name: bpy.props.StringProperty(
name="Custom Group Name",
description="Input your custom group name.",
default="",
)
@classmethod @classmethod
def poll(self, context): def poll(self, context):
return context.object is not None return context.object is not None
def execute(self, context): def execute(self, context):
# get name first
gotten_group_name = str(self.custom_group_name if self.use_custom_name else self.group_name)
# try adding
obj = context.object obj = context.object
gp = UTILS_virtools_prop.get_virtools_group(obj) if not UTILS_virtools_prop.add_virtools_group_data(obj, gotten_group_name):
item = gp.add() UTILS_functions.show_message_box(("Group name is duplicated!", ), "Duplicated Name", 'ERROR')
item.name = ""
item.group_name = str(self.group_name)
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_props_dialog(self) return wm.invoke_props_dialog(self)
def draw(self, context): def draw(self, context):
self.layout.prop(self, 'group_name') self.layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
self.layout.prop(self, 'custom_group_name')
else:
self.layout.prop(self, 'group_name')
class BALLANCE_OT_rm_virtools_group(bpy.types.Operator): class BALLANCE_OT_rm_virtools_group(bpy.types.Operator):
"""Remove a Virtools Group for Active Object.""" """Remove a Virtools Group for Active Object."""
@ -44,24 +63,14 @@ class BALLANCE_OT_rm_virtools_group(bpy.types.Operator):
if context.object is None: if context.object is None:
return False return False
try:
obj = context.object
gp = UTILS_virtools_prop.get_virtools_group(obj)
active_gp = UTILS_virtools_prop.get_active_virtools_group(obj)
data = gp[active_gp]
except:
return False
else:
return True
def execute(self, context):
obj = context.object obj = context.object
gp = UTILS_virtools_prop.get_virtools_group(obj) gp = UTILS_virtools_prop.get_virtools_group(obj)
active_gp = UTILS_virtools_prop.get_active_virtools_group(obj) active_gp = UTILS_virtools_prop.get_active_virtools_group(obj)
idx = int(active_gp) return int(active_gp) >= 0 and int(active_gp) < len(gp)
active_gp -= 1 def execute(self, context):
gp.remove(idx) obj = context.object
UTILS_virtools_prop.remove_virtools_group_data_by_index(obj, int(UTILS_virtools_prop.get_active_virtools_group(obj)))
return {'FINISHED'} return {'FINISHED'}
class BALLANCE_UL_virtools_group(bpy.types.UIList): class BALLANCE_UL_virtools_group(bpy.types.UIList):

View File

@ -345,9 +345,7 @@ propsVtGroups_availableGroups = (
"Phys_Floors", "Phys_Floors",
"Phys_FloorRails", "Phys_FloorRails",
"Phys_FloorStopper", "Phys_FloorStopper"
"CustomCKGroup"
) )

View File

@ -98,19 +98,82 @@ def get_active_virtools_group(obj):
def get_virtools_group(obj): def get_virtools_group(obj):
return obj.virtools_group return obj.virtools_group
def check_virtools_group_data(obj, probe):
for item in get_virtools_group(obj):
if probe == str(item.group_name):
return True
return False
def add_virtools_group_data(obj, new_data):
# check exist
if check_virtools_group_data(obj, new_data):
# existed, give up
return False
# "add" do not need operate active_virtools_group
data = get_virtools_group(obj)
it = data.add()
it.name = ""
it.group_name = new_data
return True
def remove_virtools_group_data(obj, rm_data):
gp = get_virtools_group(obj)
active_gp = get_active_virtools_group(obj)
for idx, item in enumerate(gp):
if rm_data == str(item.group_name):
# decrease active group if removed item is ahead of active group
if idx <= active_gp:
active_gp -= 1
# remove
gp.remove(idx)
# indicate success
return True
return False
def remove_virtools_group_data_by_index(obj, rm_idx):
gp = get_virtools_group(obj)
active_gp = get_active_virtools_group(obj)
# report error
if rm_idx >= len(gp):
return False
# remove
if rm_idx <= active_gp:
active_gp -= 1
gp.remove(rm_idx)
return True
def clear_virtools_group_data(obj):
gp = get_virtools_group(obj)
active_gp = get_active_virtools_group(obj)
gp.clear()
active_gp = 0
def fill_virtools_group_data(obj, data_list):
# clear first
clear_virtools_group_data(obj)
# if no data to add, return
if data_list is None:
return
# add one by one after check duplication
data = get_virtools_group(obj)
for item in set(data_list):
it = data.add()
it.name = ""
it.group_name = item
def get_virtools_group_data(obj): def get_virtools_group_data(obj):
return tuple(str(item.group_name) for item in get_virtools_group(obj)) return tuple(str(item.group_name) for item in get_virtools_group(obj))
def set_virtools_group_data(obj, new_data):
data = get_virtools_group(obj)
data.clear()
if new_data is not None:
for item in new_data:
it = data.add()
it.name = ""
it.group_name = item
def register_props(): def register_props():
bpy.types.Object.virtools_group = bpy.props.CollectionProperty(type=BALLANCE_PG_virtools_group) bpy.types.Object.virtools_group = bpy.props.CollectionProperty(type=BALLANCE_PG_virtools_group)
bpy.types.Object.active_virtools_group = bpy.props.IntProperty() bpy.types.Object.active_virtools_group = bpy.props.IntProperty()

View File

@ -50,6 +50,8 @@ if "bpy" in locals():
importlib.reload(OBJS_add_floors) importlib.reload(OBJS_add_floors)
if "OBJS_add_rails" in locals(): if "OBJS_add_rails" in locals():
importlib.reload(OBJS_add_rails) importlib.reload(OBJS_add_rails)
if "OBJS_group_opers" in locals():
importlib.reload(OBJS_group_opers)
if "NAMES_rename_system" in locals(): if "NAMES_rename_system" in locals():
importlib.reload(NAMES_rename_system) importlib.reload(NAMES_rename_system)
@ -62,7 +64,7 @@ if "bpy" in locals():
from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop
from . import BMFILE_export, BMFILE_import from . import BMFILE_export, BMFILE_import
from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv
from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails, OBJS_group_opers
from . import NAMES_rename_system from . import NAMES_rename_system
from . import PROPS_virtools_group, PROPS_virtools_material from . import PROPS_virtools_group, PROPS_virtools_material
@ -149,8 +151,14 @@ classes = (
PROPS_virtools_group.BALLANCE_UL_virtools_group, PROPS_virtools_group.BALLANCE_UL_virtools_group,
PROPS_virtools_group.BALLANCE_PT_virtools_group, PROPS_virtools_group.BALLANCE_PT_virtools_group,
PROPS_virtools_material.BALLANCE_OT_apply_virtools_material, PROPS_virtools_material.BALLANCE_OT_apply_virtools_material,
PROPS_virtools_material.BALLANCE_PT_virtools_material PROPS_virtools_material.BALLANCE_PT_virtools_material,
OBJS_group_opers.BALLANCE_OT_select_virtools_group,
OBJS_group_opers.BALLANCE_OT_filter_virtools_group,
OBJS_group_opers.BALLANCE_OT_ctx_set_group,
OBJS_group_opers.BALLANCE_OT_ctx_unset_group,
OBJS_group_opers.BALLANCE_OT_ctx_clear_group,
) )
def menu_func_bm_import(self, context): def menu_func_bm_import(self, context):
@ -172,6 +180,21 @@ def menu_func_ballance_add(self, context):
def menu_func_ballance_rename(self, context): def menu_func_ballance_rename(self, context):
layout = self.layout layout = self.layout
layout.menu(BALLANCE_MT_OutlinerMenu.bl_idname) layout.menu(BALLANCE_MT_OutlinerMenu.bl_idname)
def menu_func_ballance_select(self, context):
layout = self.layout
layout.separator()
layout.label(text="Ballance")
layout.operator(OBJS_group_opers.BALLANCE_OT_select_virtools_group.bl_idname, icon='SELECT_SET')
layout.operator(OBJS_group_opers.BALLANCE_OT_filter_virtools_group.bl_idname, icon='FILTER')
def menu_func_ballance_grouping(self, context):
layout = self.layout
layout.separator()
col = layout.column()
col.operator_context = 'INVOKE_DEFAULT'
col.label(text="Ballance")
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_set_group.bl_idname, icon='ADD', text="Group into...")
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_unset_group.bl_idname, icon='REMOVE', text="Ungroup from...")
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_clear_group.bl_idname, icon='TRASH', text="Clear Grouping")
def register(): def register():
@ -196,6 +219,9 @@ def register():
bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add) bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add)
bpy.types.OUTLINER_HT_header.append(menu_func_ballance_rename) bpy.types.OUTLINER_HT_header.append(menu_func_ballance_rename)
bpy.types.VIEW3D_MT_select_object.append(menu_func_ballance_select)
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func_ballance_grouping)
def unregister(): def unregister():
bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import) bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_bm_export) bpy.types.TOPBAR_MT_file_export.remove(menu_func_bm_export)
@ -204,6 +230,9 @@ def unregister():
bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add) bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add)
bpy.types.OUTLINER_HT_header.remove(menu_func_ballance_rename) bpy.types.OUTLINER_HT_header.remove(menu_func_ballance_rename)
bpy.types.VIEW3D_MT_select_object.remove(menu_func_ballance_select)
bpy.types.VIEW3D_MT_object_context_menu.remove(menu_func_ballance_grouping)
UTILS_virtools_prop.unregister_props() UTILS_virtools_prop.unregister_props()
del bpy.types.Scene.BallanceBlenderPluginProperty del bpy.types.Scene.BallanceBlenderPluginProperty