diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index d497b68..0496f23 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -111,6 +111,15 @@ FCURVE_TARGET_NAMES = { current_output = None +class BuildExportCache: + """Shared cache across all scene exports in a single build. + Created once in make.py, passed to each LeenkxExporter instance.""" + def __init__(self): + self.exported_mesh_files: set = set() + self.exported_action_files: set = set() + self.processed_mesh_names: set = set() + + class LeenkxExporter: """Export to Leenkx format. @@ -131,9 +140,11 @@ class LeenkxExporter: # Class names of referenced traits import_traits: List[str] = [] - def __init__(self, context: bpy.types.Context, filepath: str, scene: bpy.types.Scene = None, depsgraph: bpy.types.Depsgraph = None): + def __init__(self, context: bpy.types.Context, filepath: str, scene: bpy.types.Scene = None, depsgraph: bpy.types.Depsgraph = None, build_cache=None): global current_output + self.build_cache = build_cache or BuildExportCache() + self.filepath = filepath self.scene = context.scene if scene is None else scene self.depsgraph = context.evaluated_depsgraph_get() if depsgraph is None else depsgraph @@ -185,12 +196,12 @@ class LeenkxExporter: LeenkxExporter.preprocess() @classmethod - def export_scene(cls, context: bpy.types.Context, filepath: str, scene: bpy.types.Scene = None, depsgraph: bpy.types.Depsgraph = None) -> None: + def export_scene(cls, context: bpy.types.Context, filepath: str, scene: bpy.types.Scene = None, depsgraph: bpy.types.Depsgraph = None, build_cache=None) -> None: """Exports the given scene to the given file path. This is the function that is called in make.py and the entry point of the exporter.""" with lnx.profiler.Profile('profile_exporter.prof', lnx.utils.get_pref_or_default('profile_exporter', False)): - cls(context, filepath, scene, depsgraph).execute() + cls(context, filepath, scene, depsgraph, build_cache).execute() @classmethod def preprocess(cls): @@ -473,7 +484,6 @@ class LeenkxExporter: if btype is not NodeType.MESH and LeenkxExporter.option_mesh_only: return - is_local_to_linked_scene = bobject.name in self.scene.objects and bobject.name not in self.scene.collection.children and self.scene.library if bobject.type == 'CAMERA' and bobject.library: struct_name = bobject.name + '_' + (os.path.basename(self.scene.library.filepath) if self.scene.library else self.scene.name) else: @@ -1149,8 +1159,9 @@ class LeenkxExporter: self.export_particle_system_ref(bobject.particle_systems[i], out_object) aabb = bobject.data.lnx_aabb - if aabb[0] == 0 and aabb[1] == 0 and aabb[2] == 0: + if oid not in self.build_cache.processed_mesh_names or (aabb[0] == 0 and aabb[1] == 0 and aabb[2] == 0): self.calc_aabb(bobject) + self.build_cache.processed_mesh_names.add(oid) out_object['dimensions'] = [aabb[0], aabb[1], aabb[2]] # shapeKeys = LeenkxExporter.get_shape_keys(objref) @@ -1295,7 +1306,7 @@ class LeenkxExporter: skelobj.animation_data.action = action fp = self.get_meshes_file_path('action_' + armatureid + '_' + aname, compressed=LeenkxExporter.compress_enabled) assets.add(fp) - if not bdata.lnx_cached or not os.path.exists(fp): + if (not bdata.lnx_cached or not os.path.exists(fp)) and fp not in self.build_cache.exported_action_files: # Store action to use it after autobake was handled original_action = action @@ -1357,6 +1368,7 @@ class LeenkxExporter: # Save action separately action_obj = {'name': aname, 'objects': bones} lnx.utils.write_lnx(fp, action_obj) + self.build_cache.exported_action_files.add(fp) # Use relative bone constraints out_object['relative_bone_constraints'] = bdata.lnx_relative_bone_constraints @@ -1696,6 +1708,7 @@ class LeenkxExporter: mesh_obj = {'mesh_datas': [out_mesh]} lnx.utils.write_lnx(fp, mesh_obj) bobject.data.lnx_cached = True + self.build_cache.exported_mesh_files.add(fp) @staticmethod def calc_aabb(bobject): @@ -2057,7 +2070,7 @@ class LeenkxExporter: fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled) assets.add(fp) # No export necessary - if bobject.data.lnx_cached and os.path.exists(fp): + if bobject.data.lnx_cached and os.path.exists(fp) or fp in self.build_cache.exported_mesh_files: return # Mesh users have different modifier stack @@ -2227,6 +2240,7 @@ class LeenkxExporter: inner_angle = math.atan(math.tan(half_angle) * (1.0 - blend)) out_light['spot_size'] = outer_cos out_light['spot_blend'] = max(0.0001, math.cos(inner_angle) - outer_cos) + out_light['fov'] = light_ref.spot_size if light_ref.shadow_soft_size > 0.0: out_light['light_size'] = light_ref.shadow_soft_size * 10 elif objtype == 'AREA': @@ -2282,7 +2296,6 @@ class LeenkxExporter: # outside the collection, then instantiate the full object # child tree if the collection gets spawned as a whole if bobject.parent is None or bobject.parent.name not in collection.objects: - is_local_to_linked_scene = bobject.name in self.scene.objects and bobject.name not in self.scene.collection.children and self.scene.library if bobject.type == 'CAMERA': asset_name = bobject.name + '_' + (os.path.basename(self.scene.library.filepath) if self.scene.library else self.scene.name) else: @@ -2925,7 +2938,7 @@ class LeenkxExporter: if collection.name.startswith(('RigidBodyWorld', 'Trait|')): continue - if self.scene.user_of_id(collection) or collection.library and not self.scene.library or collection in self.referenced_collections: + if self.scene.user_of_id(collection) or collection in self.referenced_collections: if collection not in self.inlined_collections: self.export_collection(collection) diff --git a/leenkx/blender/lnx/make.py b/leenkx/blender/lnx/make.py index 99d220e..6a7d6c6 100644 --- a/leenkx/blender/lnx/make.py +++ b/leenkx/blender/lnx/make.py @@ -19,6 +19,7 @@ import webbrowser import bpy from lnx import assets +from lnx.exporter import BuildExportCache from lnx.exporter import LeenkxExporter import lnx.lib.make_datas import lnx.lib.server @@ -265,19 +266,21 @@ def export_data_impl(fp, sdk_path): continue for o in scene.collection.all_objects: if o.type in ('MESH', 'EMPTY'): - if o.name not in export_coll_names: + if o.name not in export_coll_names or o.library: export_coll.objects.link(o) export_coll_names.add(o.name) depsgraph = bpy.context.evaluated_depsgraph_get() bpy.data.collections.remove(export_coll) # Destroy the "zoo" collection + build_cache = BuildExportCache() + for scene in bpy.data.scenes: if scene.lnx_export: # Reset shader comparison arrays to prevent cross-scene shader merging assets.reset_shader_cons() ext = '.lz4' if LeenkxExporter.compress_enabled else '.lnx' asset_path = build_dir + '/compiled/Assets/' + lnx.utils.safestr(scene.name + "_" + os.path.basename(scene.library.filepath).replace(".blend", "") if scene.library else scene.name) + ext - LeenkxExporter.export_scene(bpy.context, asset_path, scene=scene, depsgraph=depsgraph) + LeenkxExporter.export_scene(bpy.context, asset_path, scene=scene, depsgraph=depsgraph, build_cache=build_cache) if LeenkxExporter.export_physics: physics_found = True if LeenkxExporter.export_navigation: