bl_info = { "name": "LancerEdit Model Utility Plugin", "author": "Darklighter", "version": (0, 0, 1), "blender": (4, 5, 3), "location": "3D-Viewport > Sidebar > FLLancerEdit Utility", "description": "Non-offical LancerEdit Model Utility Plugin for PC-Game Freelancer", "category": "Development", } import bmesh import bpy import re class RenameObjectsAndMeshesOperator(bpy.types.Operator): bl_idname = "object.rename_objects_and_meshes" bl_label = "Rename Objects and Mesh Data" bl_description = "Rename specified objects and their mesh data blocks, and their children" object_names: bpy.props.StringProperty( name="Object Names", description="Comma-separated list of object names to rename", default="" ) new_name: bpy.props.StringProperty( name="New Name", description="New name for objects and their mesh data blocks", default="" ) def execute(self, context): names_input = self.object_names.strip() new_name = self.new_name.strip() if not names_input or not new_name: self.report({'WARNING'}, "Please enter object names and a new name") return {'CANCELLED'} names_list = [name.strip() for name in names_input.split(",") if name.strip()] for obj_name in names_list: obj = bpy.data.objects.get(obj_name) if obj: # Rename the object old_mesh_name = obj.data.name if obj.data else None obj.name = new_name # Rename the mesh data block if it exists if obj.data: obj.data.name = new_name # Rename children objects starting with 'parent_name.' for child in bpy.data.objects: if child.name.startswith(f"{obj_name}."): suffix = child.name[len(f"{obj_name}."):] child.name = f"{new_name}.{suffix}" # Also rename their mesh data if applicable if child.data: child.data.name = f"{new_name}.{suffix}" else: self.report({'WARNING'}, f"Object '{obj_name}' not found.") self.report({'INFO'}, "Renaming of objects and mesh data completed.") return {'FINISHED'} class RenameChildObjectOperator(bpy.types.Operator): bl_idname = "object.rename_selected_child" bl_label = "Convert to Hulls/Sur Objects" bl_description = "Select Child Objects and assign to hull suffix" def execute(self, context): scene = context.scene base_name = scene.child_object_new_name.strip() if not base_name: self.report({'WARNING'}, "New name cannot be empty") return {'CANCELLED'} selected_children = [obj for obj in context.selected_objects if obj.parent] if not selected_children: self.report({'WARNING'}, "No selected child objects found") return {'CANCELLED'} # Collect existing suffixes pattern = re.compile(rf"{re.escape(base_name)}\.(\d+)\$hull") used_suffixes = [] for o in bpy.data.objects: match = pattern.match(o.name) if match: used_suffixes.append(int(match.group(1))) used_suffixes = sorted(set(used_suffixes)) suffixes_to_use = [] # Fill in gaps starting from 0 expected_suffix = 0 for suffix in used_suffixes: while expected_suffix < suffix: suffixes_to_use.append(expected_suffix) expected_suffix += 1 expected_suffix = suffix + 1 # Prepare the special material material_name = "material_0x00000000" material = bpy.data.materials.get(material_name) if material is None: material = bpy.data.materials.new(name=material_name) # Set viewport display color material.diffuse_color = (1.0, 0.0, 0.0, 0.5) # RGBA # For newer Blender versions, ensure the viewport display color is set if hasattr(material, 'viewport_color'): material.viewport_color = (1.0, 0.0, 0.0) # Set material display options material.blend_method = 'BLEND' # ensure transparency is visible # For Eevee, ensure transparency is enabled # (optional, depending on your render engine) # Assign the material to each selected child object for obj in selected_children: # Assign the material if obj.type == 'MESH': if material.name not in [mat.name for mat in obj.data.materials]: obj.data.materials.append(material) # Now, assign suffixes: suffix_index = 0 for obj in selected_children: # Check if object already has a suffix match = pattern.match(obj.name) if match: current_suffix = int(match.group(1)) if current_suffix in suffixes_to_use: # Already assigned, skip continue else: # Object already named properly, skip continue else: # Assign next available gap suffix or new suffix if suffix_index < len(suffixes_to_use): suffix_num = suffixes_to_use[suffix_index] suffix_index += 1 else: suffix_num = expected_suffix expected_suffix += 1 new_name = f"{base_name}.{suffix_num}$hull" if obj.type == 'MESH' and obj.data: obj.name = new_name obj.data.name = new_name else: self.report({'WARNING'}, f"Object {obj.name} does not have mesh data, skipped.") self.report({'INFO'}, "Selected child objects renamed successfully") return {'FINISHED'} class OBJECT_OT_create_convex(bpy.types.Operator): bl_idname = "object.create_convex" bl_label = "Create Convex" bl_description = "Delete faces and edges, then create a convex hull of the active object" def execute(self, context): obj = context.active_object if not obj or obj.type != 'MESH': self.report({'WARNING'}, "No active mesh object selected") return {'CANCELLED'} # Make object active and selected bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') obj.select_set(True) context.view_layer.objects.active = obj # Enter edit mode bpy.ops.object.mode_set(mode='EDIT') mesh = bmesh.from_edit_mesh(obj.data) # Delete all faces and edges bmesh.ops.delete(mesh, geom=[f for f in mesh.faces] + [e for e in mesh.edges], context='EDGES_FACES') bmesh.update_edit_mesh(obj.data) # Exit edit mode bpy.ops.object.mode_set(mode='OBJECT') # Now, perform the convex hull operation on the vertices # Create a new bmesh from the current mesh bm = bmesh.new() bm.from_mesh(obj.data) # Perform convex hull bmesh.ops.convex_hull(bm, input=bm.verts) # Write back to the mesh bm.to_mesh(obj.data) bm.free() self.report({'INFO'}, "Convex hull created") return {'FINISHED'} # Alternatively, directly run the convex hull operator: bpy.ops.object.select_all(action='DESELECT') obj.select_set(True) context.view_layer.objects.active = obj bpy.ops.object.convert(target='MESH') bpy.ops.mesh.convex_hull() self.report({'INFO'}, "Convex hull created") return {'FINISHED'} class LancerEditHelpersPanel(bpy.types.Panel): bl_label = "LancerEdit Model Utility Plugin" bl_idname = "OBJECT_PT_rename_objects_meshes" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = 'LancerEdit Utility' def draw(self, context): layout = self.layout scene = context.scene # Existing renaming section layout.label(text="Rename Parent Object:") layout.prop(scene, "object_names") layout.prop(scene, "new_name") op = layout.operator("object.rename_objects_and_meshes") op.object_names = scene.object_names op.new_name = scene.new_name # Section for renaming selected child object layout.separator() layout.label(text="Surface Creation Kit:") # Button for creating Convex Hull Object layout.operator("object.create_convex") layout.separator() # Input Dialogue for Sur Name layout.prop(scene, "child_object_new_name") # Button for Convert to Hull layout.operator("object.rename_selected_child") def register(): bpy.utils.register_class(LancerEditHelpersPanel) bpy.utils.register_class(RenameObjectsAndMeshesOperator) bpy.utils.register_class(RenameChildObjectOperator) bpy.utils.register_class(OBJECT_OT_create_convex) bpy.types.Scene.object_names = bpy.props.StringProperty( name="Parent Name", description="Comma-separated list of object names", default="" ) bpy.types.Scene.new_name = bpy.props.StringProperty( name="Change Name", description="New name for objects and their mesh data", default="" ) bpy.types.Scene.child_object_new_name = bpy.props.StringProperty( name="Sur Name", description="New name for the selected child object", default="" ) def unregister(): bpy.utils.unregister_class(LancerEditHelpersPanel) bpy.utils.unregister_class(RenameObjectsAndMeshesOperator) bpy.utils.unregister_class(RenameChildObjectOperator) bpy.utils.unregister_class(OBJECT_OT_create_convex) del bpy.types.Scene.object_names del bpy.types.Scene.new_name del bpy.types.Scene.child_object_new_name if __name__ == "__main__": register()