Skip to content
This repository was archived by the owner on May 6, 2026. It is now read-only.

Commit edfc085

Browse files
committed
Refactored Pipeline to use multiprocessing and utilize further performance tweaks
Added additional clean up steps into the combined pipeline module. Added a step to skip LOD processing when the LOD count is set to zero. Refactored the pipeline to load high-poly models only when required, avoiding persistent caching throughout the entire process. Refactored unwrapping by incorporating multiprocessing with xatlas, significantly improving performance. Refactored outer boundary detection to mark only the outermost boundary, avoiding unnecessary markings.
1 parent 8203c8f commit edfc085

5 files changed

Lines changed: 280 additions & 105 deletions

File tree

enviro_lod_tools/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
bl_info = {
66
"name": "Environment LOD Tools",
77
"author": "Nico Breycha",
8-
"version": (0, 0, 3),
8+
"version": (0, 0, 4),
99
"blender": (4, 0, 0),
1010
"description": "Generates LODs for selected mesh objects across multiple scripts.",
1111
"category": "Object",

enviro_lod_tools/addons/ds_blender_combined_plugin.py

Lines changed: 70 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
bl_info = {
1414
"name": "Automated LOD Generation Tool",
1515
"author": "Nico Breycha",
16-
"version": (0, 0, 3),
16+
"version": (0, 0, 4),
1717
"blender": (4, 0, 0),
1818
"location": "View3D > Sidebar > Tool Tab",
1919
"description": "Combines the Operator from all the other plugins.",
@@ -26,13 +26,48 @@ class OBJECT_OT_lod_pipeline(bpy.types.Operator):
2626
bl_label = COMB_LABEL
2727

2828
def execute(self, context):
29-
def _select_all_except_original():
30-
"""Select all objects except the original mesh."""
31-
for _obj in bpy.data.objects:
32-
if _obj != original_mesh:
29+
def import_and_prepare_original_mesh(filepath, rotation_correction, keep_original_name = False):
30+
# Get list of existing objects before import
31+
existing_objects = set(bpy.data.objects)
32+
33+
# Import the model
34+
bpy.ops.wm.obj_import(filepath=filepath)
35+
36+
# Get the list of new objects
37+
imported_objects = [_obj for _obj in bpy.data.objects if _obj not in existing_objects]
38+
39+
# Deselect all
40+
bpy.ops.object.select_all(action='DESELECT')
41+
42+
# If it's multiple meshes, join them into one.
43+
for _obj in imported_objects:
44+
if _obj.type == 'MESH':
3345
_obj.select_set(True)
34-
else:
35-
_obj.select_set(False)
46+
bpy.context.view_layer.objects.active = _obj
47+
48+
bpy.ops.object.join()
49+
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
50+
51+
# Apply rotation correction if needed
52+
_obj = bpy.context.active_object
53+
54+
if rotation_correction[0] != 0:
55+
_obj.rotation_euler[0] = math.radians(rotation_correction[0])
56+
if rotation_correction[1] != 0:
57+
_obj.rotation_euler[1] = math.radians(rotation_correction[1])
58+
if rotation_correction[2] != 0:
59+
_obj.rotation_euler[2] = math.radians(rotation_correction[2])
60+
61+
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
62+
63+
# Rename the original mesh
64+
imported_mesh = bpy.context.active_object
65+
66+
if not keep_original_name:
67+
imported_mesh.name = "original_mesh"
68+
imported_mesh.data.name = "original_mesh"
69+
70+
return imported_mesh
3671

3772
clear_scene()
3873

@@ -94,65 +129,24 @@ def _select_all_except_original():
94129
context.scene.baker_settings.texture_margin = baker_settings_comb.texture_margin
95130
context.scene.baker_settings.save_path = baker_settings_comb.save_path
96131

97-
# Import Model
98-
bpy.ops.wm.obj_import(filepath=import_fp_comb)
99-
100-
bpy.ops.object.select_all(action='DESELECT')
101-
102-
# If its multiple meshes, join them into one.
103-
for obj in bpy.data.objects:
104-
if obj.type == 'MESH':
105-
obj.select_set(True)
106-
bpy.context.view_layer.objects.active = obj
107-
108-
bpy.ops.object.join()
109-
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
110-
111-
# Apply rotation correction if needed
112-
obj = bpy.context.active_object
113-
114-
if rot_correction_comb[0] != 0:
115-
obj.rotation_euler[0] = math.radians(rot_correction_comb[0])
116-
if rot_correction_comb[1] != 0:
117-
obj.rotation_euler[1] = math.radians(rot_correction_comb[1])
118-
if rot_correction_comb[2] != 0:
119-
obj.rotation_euler[2] = math.radians(rot_correction_comb[2])
120-
121-
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
122-
123-
124-
# Rename the original mesh. We will use it later for baking.
125-
original_mesh = bpy.context.active_object
126-
orig_name = original_mesh.name
127-
original_mesh.name = "original_mesh"
128-
original_mesh.data.name = "original_mesh"
129-
context.scene.baker_settings.highpoly_mesh_name = original_mesh.name
130-
131-
# Create a copy for the mesh we will work on.
132-
bpy.ops.object.duplicate()
133-
working_mesh = bpy.context.active_object
134-
working_mesh.name = orig_name
135-
working_mesh.data.name = orig_name
132+
working_mesh = import_and_prepare_original_mesh(import_fp_comb, rot_correction_comb, keep_original_name=True)
136133

137134
# Make sure only the working mesh is selected.
138135
bpy.ops.object.select_all(action='DESELECT')
139136
working_mesh.select_set(True)
140137
bpy.context.view_layer.objects.active = working_mesh
141138

142139
# Execute Operators
143-
print("Starting Cleanup")
144140
launch_operator_by_name(CLEANUP_IDNAME)
145141

146-
print("Starting Slicing")
147142
launch_operator_by_name(SLICE_IDNAME)
148143

149-
150144
# Ensure Target Polycount set by the user as intial polycount.
151-
parts = []
145+
parts = set()
152146

153147
for obj in bpy.data.objects:
154-
if obj != original_mesh:
155-
parts.append(obj)
148+
if obj.type == "MESH":
149+
parts.add(obj)
156150

157151
# Clamp part count to not exceed initial reduction count.
158152
target_part_pc = initial_reduction_comb / num_of_modules_comb
@@ -165,34 +159,42 @@ def _select_all_except_original():
165159
vg = vertex_group_from_outer_boundary(part)
166160
decimate_object(part, perc_red, iterations=1, vg_name=vg)
167161

168-
_select_all_except_original()
162+
for part in parts:
163+
part.select_set(True)
169164

170165
launch_operator_by_name(LOD_IDNAME)
171166

172-
_select_all_except_original()
167+
objects_to_bake = {obj for obj in bpy.data.objects if obj.type == "MESH"}
168+
169+
for obj in objects_to_bake:
170+
obj.select_set(True)
173171

174172
launch_operator_by_name(UNWRAP_IDNAME)
175173

176-
# Save .blend File after Unwrap.
177-
blend_file_path = os.path.join(export_fp_comb, "bake_scene.blend")
178-
bpy.ops.wm.save_as_mainfile(filepath=blend_file_path, check_existing=False, compress=True)
174+
original_mesh = import_and_prepare_original_mesh(import_fp_comb, rot_correction_comb, keep_original_name=False)
175+
context.scene.baker_settings.highpoly_mesh_name = original_mesh.name
179176

180-
_select_all_except_original()
177+
for obj in objects_to_bake:
178+
obj.vertex_groups.clear() # Clear a little non-relevant data along the way.
179+
obj.select_set(True)
181180

182181
launch_operator_by_name(BAKE_IDNAME)
183182

183+
# Remove the original mesh from the scene before export.
184+
original_mesh.select_set(True)
185+
bpy.ops.object.delete()
186+
184187
# Export newly created objects.
185-
for obj in bpy.data.objects:
186-
if obj.type == 'MESH' and obj != original_mesh:
187-
# Deselect all objects
188-
bpy.ops.object.select_all(action='DESELECT')
188+
for obj in objects_to_bake:
189+
# Deselect all objects
190+
bpy.ops.object.select_all(action='DESELECT')
189191

190-
# Select the object to export
191-
obj.select_set(True)
192-
bpy.context.view_layer.objects.active = obj
192+
# Select the object to export
193+
obj.select_set(True)
194+
bpy.context.view_layer.objects.active = obj
193195

194-
export_path = os.path.join(export_fp_comb, obj.name + '.obj')
195-
bpy.ops.wm.obj_export(filepath=export_path, export_selected_objects=True)
196+
export_path = os.path.join(export_fp_comb, obj.name + '.obj')
197+
bpy.ops.wm.obj_export(filepath=export_path, export_selected_objects=True)
196198

197199
# Restore Operator Properties
198200
context.scene.initial_reduction = restore_dict["initial_reduction"]
@@ -209,7 +211,7 @@ def _select_all_except_original():
209211
context.scene.baker_settings.texture_margin = restore_dict["baker_settings"].texture_margin
210212
context.scene.baker_settings.save_path = restore_dict["baker_settings"].save_path
211213

212-
# Save .blend File
214+
blend_file_path = os.path.join(export_fp_comb, "cleaned_scene.blend")
213215
bpy.ops.wm.save_as_mainfile(filepath=blend_file_path, check_existing=False, compress=True)
214216

215217
self.report({'INFO'}, "Export completed successfully.")

enviro_lod_tools/addons/ds_blender_lod_plug.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
bl_info = {
77
"name": "LOD Generator",
88
"author": "Nico Breycha",
9-
"version": (0, 0, 4),
9+
"version": (0, 0, 5),
1010
"blender": (4, 0, 0),
1111
"location": "View3D > Sidebar > Tool Tab",
1212
"description": "Generates levels of detail (LODs) for selected mesh objects.",
@@ -126,6 +126,11 @@ def execute(self, context):
126126
:rtype: set
127127
"""
128128
lod_count = context.scene.lod_count
129+
130+
if lod_count == 0:
131+
self.report({'INFO'}, "No LODs to generate.")
132+
return {'FINISHED'}
133+
129134
reduction_percentage = context.scene.reduction_percentage
130135
lod_generator = LODGenerator(lod_count, reduction_percentage)
131136
lod_generator.generate_lods(context)

0 commit comments

Comments
 (0)