diff --git a/.gitattributes b/.gitattributes index 29489e1..593ce15 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,5 +2,6 @@ *.png binary # our generated mesh should be save as binary *.bin binary -# the compressed json data should be binary +# the raw json data should be binary +# although i edit it manually bbp_ng/raw_jsons/*.json binary diff --git a/bbp_ng/OP_OBJECT_virtools_group.py b/bbp_ng/OP_OBJECT_virtools_group.py new file mode 100644 index 0000000..2b450d7 --- /dev/null +++ b/bbp_ng/OP_OBJECT_virtools_group.py @@ -0,0 +1,198 @@ +import bpy +import enum +from . import PROP_virtools_group +from . import UTIL_functions + +#region Select by Group + +class SelectMode(enum.IntEnum): + Set = enum.auto() + Extend = enum.auto() + Subtract = enum.auto() + Difference = enum.auto() + Intersect = enum.auto() +_g_SelectModeDesc: dict[SelectMode, tuple[str, str, str]] = { + SelectMode.Set: ('Set', 'Sets a new selection.', 'SELECT_SET'), + SelectMode.Extend: ('Extend', 'Adds newly selected items to the existing selection.', 'SELECT_EXTEND'), + SelectMode.Subtract: ('Subtract', 'Removes newly selected items from the existing selection.', 'SELECT_SUBTRACT'), + SelectMode.Difference: ('Invert', 'Inverts the selection.', 'SELECT_DIFFERENCE'), + SelectMode.Intersect: ('Intersect', 'Selects items that intersect with the existing selection.', 'SELECT_INTERSECT') +} +_g_EnumHelper_SelectMode: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper( + SelectMode, + lambda x: str(x.value), + lambda x: SelectMode(int(x)), + lambda x: _g_SelectModeDesc[x][0], + lambda x: _g_SelectModeDesc[x][1], + lambda x: _g_SelectModeDesc[x][2] +) + +class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_group.SharedGroupNameInputProperties): + """Select Objects by Virtools Group""" + bl_idname = "bbp.select_object_by_virtools_group" + bl_label = "Select by Virtools Group" + bl_options = {'UNDO'} + + selection_mode: bpy.props.EnumProperty( + name = "Mode", + description = "Selection mode", + items = _g_EnumHelper_SelectMode.generate_items(), + default = _g_EnumHelper_SelectMode.to_selection(SelectMode.Intersect) + ) + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + def execute(self, context): + _select_object_by_virtools_group( + self.general_get_group_name(), + _g_EnumHelper_SelectMode.get_selection(self.selection_mode) + ) + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + layout.label(text='Selection Mode') + layout.prop(self, 'selection_mode', expand = True) + + layout.separator() + layout.label(text='Group Parameters') + self.draw_group_name_input(layout) + +def _select_object_by_virtools_group(group_name: str, mode: SelectMode) -> None: + match(mode): + case SelectMode.Set: + # iterate all objects and directly set + for obj in bpy.context.scene.objects: + # check group and decide whether select this obj + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + obj.select_set(gp.contain_group(group_name)) + case SelectMode.Extend: + # also iterate all objects + for obj in bpy.context.scene.objects: + # but only increase selection, for selected object, skip check + if obj.select_get(): continue + # if not selected, check whether add it. + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + if gp.contain_group(group_name): + obj.select_set(True) + case SelectMode.Subtract: + # subtract only involving selected item. so we get selected objest first + # and copy it (because we need modify it) + # and iterate it to reduce useless operations + selected = bpy.context.selected_objects[:] + for obj in selected: + # remove matched only + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + if gp.contain_group(group_name): + obj.select_set(False) + case SelectMode.Difference: + # construct a selected obj set for convenient operations + selected_set = set(bpy.context.selected_objects) + # iterate all objects + for obj in bpy.context.scene.objects: + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + # use xor to select + # in_selected XOR in_group + obj.select_set((obj in selected_set) ^ gp.contain_group(group_name)) + case SelectMode.Intersect: + # like subtract, only iterate selected obj + selected = bpy.context.selected_objects[:] + for obj in selected: + # but remove not matched + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + if not gp.contain_group(group_name): + obj.select_set(False) + case _: + raise UTIL_functions.BBPException('invalid selection mode') + +#endregion + +#region Objects Group Opers + +class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.SharedGroupNameInputProperties): + """Grouping Selected Objects""" + bl_idname = "bbp.add_objects_virtools_group" + bl_label = "Grouping Objects" + bl_options = {'UNDO'} + + @classmethod + def poll(self, context): + return len(bpy.context.selected_objects) != 0 + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + def execute(self, context): + group_name: str = self.general_get_group_name() + for obj in bpy.context.selected_objects: + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + gp.add_group(group_name) + return {'FINISHED'} + + def draw(self, context): + self.draw_group_name_input(self.layout) + +class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.SharedGroupNameInputProperties): + """Ungrouping Selected Objects""" + bl_idname = "bbp.rm_objects_virtools_group" + bl_label = "Ungrouping Objects" + bl_options = {'UNDO'} + + @classmethod + def poll(self, context): + return len(bpy.context.selected_objects) != 0 + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + def execute(self, context): + group_name: str = self.general_get_group_name() + for obj in bpy.context.selected_objects: + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + gp.remove_group(group_name) + return {'FINISHED'} + + def draw(self, context): + self.draw_group_name_input(self.layout) + +class BBP_OT_clear_objects_virtools_group(bpy.types.Operator): + """Clear Virtools Groups on Selected Objects""" + bl_idname = "bbp.clear_objects_virtools_group" + bl_label = "Clear All Groups" + bl_options = {'UNDO'} + + @classmethod + def poll(self, context): + return len(bpy.context.selected_objects) != 0 + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_confirm(self, event) + + def execute(self, context): + # iterate object + for obj in bpy.context.selected_objects: + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: + gp.clear_groups() + return {'FINISHED'} + +#endregion + +def register(): + bpy.utils.register_class(BBP_OT_select_object_by_virtools_group) + + bpy.utils.register_class(BBP_OT_add_objects_virtools_group) + bpy.utils.register_class(BBP_OT_rm_objects_virtools_group) + bpy.utils.register_class(BBP_OT_clear_objects_virtools_group) + +def unregister(): + bpy.utils.unregister_class(BBP_OT_clear_objects_virtools_group) + bpy.utils.unregister_class(BBP_OT_rm_objects_virtools_group) + bpy.utils.unregister_class(BBP_OT_add_objects_virtools_group) + + bpy.utils.unregister_class(BBP_OT_select_object_by_virtools_group) + diff --git a/bbp_ng/OP_UV_flatten_uv.py b/bbp_ng/OP_UV_flatten_uv.py index 8a5d0b2..28ed605 100644 --- a/bbp_ng/OP_UV_flatten_uv.py +++ b/bbp_ng/OP_UV_flatten_uv.py @@ -301,6 +301,8 @@ def _real_flatten_uv(mesh: bpy.types.Mesh, reference_edge: int, scale_data: _Fla # just get abs for the u component _set_face_vertex_uv(face, uv_layer, idx, (abs(ppuv.x), ppuv.y)) + # show the updates in the viewport + bmesh.update_edit_mesh(mesh) # return process result return no_processed_count diff --git a/bbp_ng/PROP_virtools_group.py b/bbp_ng/PROP_virtools_group.py index 001d253..2fa5e5b 100644 --- a/bbp_ng/PROP_virtools_group.py +++ b/bbp_ng/PROP_virtools_group.py @@ -290,7 +290,7 @@ class BBP_UL_virtools_groups(bpy.types.UIList): def draw_item(self, context, layout: bpy.types.UILayout, data, item: BBP_PG_virtools_group, icon, active_data, active_propname): layout.label(text = item.group_name, translate = False, icon_value = _get_group_icon_by_name(item.group_name)) -class BBP_OT_add_virtools_groups(bpy.types.Operator, SharedGroupNameInputProperties): +class BBP_OT_add_virtools_group(bpy.types.Operator, SharedGroupNameInputProperties): """Add a Virtools Group for Active Object.""" bl_idname = "bbp.add_virtools_groups" bl_label = "Add to Virtools Groups" @@ -313,7 +313,7 @@ class BBP_OT_add_virtools_groups(bpy.types.Operator, SharedGroupNameInputPropert def draw(self, context): self.draw_group_name_input(self.layout) -class BBP_OT_rm_virtools_groups(bpy.types.Operator): +class BBP_OT_rm_virtools_group(bpy.types.Operator): """Remove a Virtools Group for Active Object.""" bl_idname = "bbp.rm_virtools_groups" bl_label = "Remove from Virtools Groups" @@ -389,8 +389,8 @@ class BBP_PT_virtools_groups(bpy.types.Panel): ) col = row.column(align=True) - col.operator(BBP_OT_add_virtools_groups.bl_idname, icon='ADD', text="") - col.operator(BBP_OT_rm_virtools_groups.bl_idname, icon='REMOVE', text="") + col.operator(BBP_OT_add_virtools_group.bl_idname, icon='ADD', text="") + col.operator(BBP_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="") col.separator() col.operator(BBP_OT_clear_virtools_groups.bl_idname, icon='TRASH', text="") @@ -400,8 +400,8 @@ def register(): # register all classes bpy.utils.register_class(BBP_PG_virtools_group) bpy.utils.register_class(BBP_UL_virtools_groups) - bpy.utils.register_class(BBP_OT_add_virtools_groups) - bpy.utils.register_class(BBP_OT_rm_virtools_groups) + bpy.utils.register_class(BBP_OT_add_virtools_group) + bpy.utils.register_class(BBP_OT_rm_virtools_group) bpy.utils.register_class(BBP_OT_clear_virtools_groups) bpy.utils.register_class(BBP_PT_virtools_groups) @@ -416,7 +416,7 @@ def unregister(): bpy.utils.unregister_class(BBP_PT_virtools_groups) bpy.utils.unregister_class(BBP_OT_clear_virtools_groups) - bpy.utils.unregister_class(BBP_OT_rm_virtools_groups) - bpy.utils.unregister_class(BBP_OT_add_virtools_groups) + bpy.utils.unregister_class(BBP_OT_rm_virtools_group) + bpy.utils.unregister_class(BBP_OT_add_virtools_group) bpy.utils.unregister_class(BBP_UL_virtools_groups) bpy.utils.unregister_class(BBP_PG_virtools_group) diff --git a/bbp_ng/__init__.py b/bbp_ng/__init__.py index 56c8578..cf6f196 100644 --- a/bbp_ng/__init__.py +++ b/bbp_ng/__init__.py @@ -33,7 +33,7 @@ from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, P from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools from . import OP_UV_flatten_uv, OP_UV_rail_uv from . import OP_ADDS_component, OP_ADDS_bme -from . import OP_OBJECT_legacy_align +from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group #region Menu @@ -46,9 +46,15 @@ class BBP_MT_View3DMenu(bpy.types.Menu): def draw(self, context): layout = self.layout + layout.label(text = 'UV', icon = 'UV') layout.operator(OP_UV_flatten_uv.BBP_OT_flatten_uv.bl_idname) layout.operator(OP_UV_rail_uv.BBP_OT_rail_uv.bl_idname) + layout.separator() + layout.label(text = 'Align', icon = 'SNAP_ON') layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname) + layout.separator() + layout.label(text = 'Select', icon = 'SELECT_SET') + layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname) class BBP_MT_AddBmeMenu(bpy.types.Menu): """Add Ballance Floor""" @@ -95,27 +101,44 @@ class BBP_MT_AddComponentsMenu(bpy.types.Menu): MenuDrawer_t = typing.Callable[[typing.Any, typing.Any], None] -def menu_drawer_import(self, context): +def menu_drawer_import(self, context) -> None: layout: bpy.types.UILayout = self.layout layout.operator(OP_IMPORT_bmfile.BBP_OT_import_bmfile.bl_idname, text = "Ballance Map (.bmx)") layout.operator(OP_IMPORT_virtools.BBP_OT_import_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)") -def menu_drawer_export(self, context): +def menu_drawer_export(self, context) -> None: layout: bpy.types.UILayout = self.layout layout.operator(OP_EXPORT_bmfile.BBP_OT_export_bmfile.bl_idname, text = "Ballance Map (.bmx)") layout.operator(OP_EXPORT_virtools.BBP_OT_export_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)") -def menu_drawer_view3d(self, context): +def menu_drawer_view3d(self, context) -> None: layout: bpy.types.UILayout = self.layout layout.menu(BBP_MT_View3DMenu.bl_idname) -def menu_drawer_add(self, context): +def menu_drawer_add(self, context) -> None: layout: bpy.types.UILayout = self.layout layout.separator() layout.label(text="Ballance") layout.menu(BBP_MT_AddBmeMenu.bl_idname, icon='MESH_CUBE') layout.menu(BBP_MT_AddRailMenu.bl_idname, icon='MESH_CIRCLE') layout.menu(BBP_MT_AddComponentsMenu.bl_idname, icon='MESH_ICOSPHERE') + +def menu_drawer_grouping(self, context) -> None: + layout: bpy.types.UILayout = self.layout + layout.separator() + + # NOTE: because outline context may change operator context + # so it will cause no popup window when click operator in outline. + # thus we create a sub layout and set its operator context as 'INVOKE_DEFAULT' + # thus, all operators can pop up normally. + col = layout.column() + col.operator_context = 'INVOKE_DEFAULT' + + col.label(text="Virtools Group") + col.operator(OP_OBJECT_virtools_group.BBP_OT_add_objects_virtools_group.bl_idname, icon = 'ADD', text = "Group into...") + col.operator(OP_OBJECT_virtools_group.BBP_OT_rm_objects_virtools_group.bl_idname, icon = 'REMOVE', text = "Ungroup from...") + col.operator(OP_OBJECT_virtools_group.BBP_OT_clear_objects_virtools_group.bl_idname, icon = 'TRASH', text = "Clear All Groups") + #endregion #region Register and Unregister. @@ -141,6 +164,10 @@ g_BldMenus: tuple[MenuEntry, ...] = ( MenuEntry(bpy.types.TOPBAR_MT_file_import, True, menu_drawer_import), MenuEntry(bpy.types.TOPBAR_MT_file_export, True, menu_drawer_export), MenuEntry(bpy.types.VIEW3D_MT_add, True, menu_drawer_add), + + # register double (for 2 menus) + MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_grouping), + MenuEntry(bpy.types.OUTLINER_MT_object, True, menu_drawer_grouping), ) def register() -> None: @@ -166,6 +193,7 @@ def register() -> None: OP_ADDS_bme.register() OP_OBJECT_legacy_align.register() + OP_OBJECT_virtools_group.register() # register other classes for cls in g_BldClasses: @@ -188,6 +216,7 @@ def unregister() -> None: bpy.utils.unregister_class(cls) # unregister modules + OP_OBJECT_virtools_group.unregister() OP_OBJECT_legacy_align.unregister() OP_ADDS_bme.unregister()