From 6ae8899912e23bcf037984dafa0ea889db234b69 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sun, 5 Jan 2025 20:06:40 +0800 Subject: [PATCH] feat: add new operator about convert curve to mesh with group infos. - add a new operator which can converte selected object to mesh, and if object is curve and has bevel object, it will copy the virtools group info of bevel object at the same time. - add a hint in virtools group panel to tell user that the virtools group of non-mesh object will not be saved. --- bbp_ng/OP_OBJECT_snoop_group_then_to_mesh.py | 50 ++++++++++++++++++++ bbp_ng/PROP_virtools_group.py | 8 +++- bbp_ng/__init__.py | 12 ++++- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 bbp_ng/OP_OBJECT_snoop_group_then_to_mesh.py diff --git a/bbp_ng/OP_OBJECT_snoop_group_then_to_mesh.py b/bbp_ng/OP_OBJECT_snoop_group_then_to_mesh.py new file mode 100644 index 0000000..e6e2245 --- /dev/null +++ b/bbp_ng/OP_OBJECT_snoop_group_then_to_mesh.py @@ -0,0 +1,50 @@ +import bpy +import typing +from . import PROP_virtools_group + +class BBP_OT_snoop_group_then_to_mesh(bpy.types.Operator): + """Convert selected objects into mesh objects and try to copy the Virtools Group infos of their associated curve bevel object if they have. """ + bl_idname = "bbp.snoop_group_then_to_mesh" + bl_label = "Snoop Group then to Mesh" + bl_options = {'UNDO'} + + @classmethod + def poll(cls, context): + return len(context.selected_objects) != 0 + + def execute(self, context): + for obj in context.selected_objects: + # skip all non-curve object + if obj.type != 'CURVE': continue + + # fetch curve data block + curve: bpy.types.Curve = typing.cast(bpy.types.Curve, obj.data) + + # if bevel mode is not object, skip + if curve.bevel_mode != 'OBJECT': continue + # if bevel object is None, skip + bevel_obj: bpy.types.Object | None = curve.bevel_object + if bevel_obj is None: continue + + # copy bevel object group info into current object + # MARK: VirtoolsGroupsHelper is self-mutex. + # we only can operate one VirtoolsGroupsHelper at the same time. + # so we extract bevel object group infos then apply to target later. + group_infos: tuple[str, ...] + with PROP_virtools_group.VirtoolsGroupsHelper(bevel_obj) as bevel_gp: + group_infos = tuple(bevel_gp.iterate_groups()) + with PROP_virtools_group.VirtoolsGroupsHelper(obj) as this_gp: + this_gp.clear_groups() + this_gp.add_groups(group_infos) + + # convert all selected object to mesh + # no matter the success of copying virtools group infos and whether selected object is curve + bpy.ops.object.convert(target = 'MESH') + + return {'FINISHED'} + +def register() -> None: + bpy.utils.register_class(BBP_OT_snoop_group_then_to_mesh) + +def unregister() -> None: + bpy.utils.unregister_class(BBP_OT_snoop_group_then_to_mesh) diff --git a/bbp_ng/PROP_virtools_group.py b/bbp_ng/PROP_virtools_group.py index dbc3e74..9fe5240 100644 --- a/bbp_ng/PROP_virtools_group.py +++ b/bbp_ng/PROP_virtools_group.py @@ -377,7 +377,13 @@ class BBP_PT_virtools_groups(bpy.types.Panel): def draw(self, context): layout = self.layout - target = bpy.context.active_object + target = typing.cast(bpy.types.Object, context.active_object) + + # notify on non-mesh object + if target.type != 'MESH': + layout.label(text = 'Virtools Group is invalid on non-mesh object!', icon = 'ERROR') + + # draw main body row = layout.row() row.template_list( diff --git a/bbp_ng/__init__.py b/bbp_ng/__init__.py index 0769cb0..6393fcd 100644 --- a/bbp_ng/__init__.py +++ b/bbp_ng/__init__.py @@ -22,7 +22,7 @@ from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_ from . import OP_UV_flatten_uv, OP_UV_rail_uv from . import OP_MTL_fix_material from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail -from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_naming_convention +from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_snoop_group_then_to_mesh, OP_OBJECT_naming_convention #region Menu @@ -148,6 +148,12 @@ def menu_drawer_grouping(self, context) -> None: 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") +def menu_drawer_snoop_then_conv(self, context) -> None: + layout: bpy.types.UILayout = self.layout + layout.separator() + layout.label(text = "Ballance") + layout.operator(OP_OBJECT_snoop_group_then_to_mesh.BBP_OT_snoop_group_then_to_mesh.bl_idname, icon = 'OUTLINER_OB_MESH') + def menu_drawer_naming_convention(self, context) -> None: layout: bpy.types.UILayout = self.layout layout.separator() @@ -187,6 +193,8 @@ g_BldMenus: tuple[MenuEntry, ...] = ( MenuEntry(bpy.types.TOPBAR_MT_file_export, True, menu_drawer_export), MenuEntry(bpy.types.VIEW3D_MT_add, True, menu_drawer_add), + MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_snoop_then_conv), + # 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), @@ -224,6 +232,7 @@ def register() -> None: OP_OBJECT_legacy_align.register() OP_OBJECT_virtools_group.register() + OP_OBJECT_snoop_group_then_to_mesh.register() OP_OBJECT_naming_convention.register() # register other classes @@ -248,6 +257,7 @@ def unregister() -> None: # unregister modules OP_OBJECT_naming_convention.unregister() + OP_OBJECT_snoop_group_then_to_mesh.unregister() OP_OBJECT_virtools_group.unregister() OP_OBJECT_legacy_align.unregister()