This commit is contained in:
2026-05-16 14:48:36 -07:00
parent 8ccebf4814
commit 6aa0f19fb2
2 changed files with 27 additions and 11 deletions

View File

@ -111,6 +111,15 @@ FCURVE_TARGET_NAMES = {
current_output = None 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: class LeenkxExporter:
"""Export to Leenkx format. """Export to Leenkx format.
@ -131,9 +140,11 @@ class LeenkxExporter:
# Class names of referenced traits # Class names of referenced traits
import_traits: List[str] = [] 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 global current_output
self.build_cache = build_cache or BuildExportCache()
self.filepath = filepath self.filepath = filepath
self.scene = context.scene if scene is None else scene self.scene = context.scene if scene is None else scene
self.depsgraph = context.evaluated_depsgraph_get() if depsgraph is None else depsgraph self.depsgraph = context.evaluated_depsgraph_get() if depsgraph is None else depsgraph
@ -185,12 +196,12 @@ class LeenkxExporter:
LeenkxExporter.preprocess() LeenkxExporter.preprocess()
@classmethod @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 """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 function that is called in make.py and the entry point of the
exporter.""" exporter."""
with lnx.profiler.Profile('profile_exporter.prof', lnx.utils.get_pref_or_default('profile_exporter', False)): 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 @classmethod
def preprocess(cls): def preprocess(cls):
@ -473,7 +484,6 @@ class LeenkxExporter:
if btype is not NodeType.MESH and LeenkxExporter.option_mesh_only: if btype is not NodeType.MESH and LeenkxExporter.option_mesh_only:
return 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: 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) struct_name = bobject.name + '_' + (os.path.basename(self.scene.library.filepath) if self.scene.library else self.scene.name)
else: else:
@ -1149,8 +1159,9 @@ class LeenkxExporter:
self.export_particle_system_ref(bobject.particle_systems[i], out_object) self.export_particle_system_ref(bobject.particle_systems[i], out_object)
aabb = bobject.data.lnx_aabb 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.calc_aabb(bobject)
self.build_cache.processed_mesh_names.add(oid)
out_object['dimensions'] = [aabb[0], aabb[1], aabb[2]] out_object['dimensions'] = [aabb[0], aabb[1], aabb[2]]
# shapeKeys = LeenkxExporter.get_shape_keys(objref) # shapeKeys = LeenkxExporter.get_shape_keys(objref)
@ -1295,7 +1306,7 @@ class LeenkxExporter:
skelobj.animation_data.action = action skelobj.animation_data.action = action
fp = self.get_meshes_file_path('action_' + armatureid + '_' + aname, compressed=LeenkxExporter.compress_enabled) fp = self.get_meshes_file_path('action_' + armatureid + '_' + aname, compressed=LeenkxExporter.compress_enabled)
assets.add(fp) 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 # Store action to use it after autobake was handled
original_action = action original_action = action
@ -1357,6 +1368,7 @@ class LeenkxExporter:
# Save action separately # Save action separately
action_obj = {'name': aname, 'objects': bones} action_obj = {'name': aname, 'objects': bones}
lnx.utils.write_lnx(fp, action_obj) lnx.utils.write_lnx(fp, action_obj)
self.build_cache.exported_action_files.add(fp)
# Use relative bone constraints # Use relative bone constraints
out_object['relative_bone_constraints'] = bdata.lnx_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]} mesh_obj = {'mesh_datas': [out_mesh]}
lnx.utils.write_lnx(fp, mesh_obj) lnx.utils.write_lnx(fp, mesh_obj)
bobject.data.lnx_cached = True bobject.data.lnx_cached = True
self.build_cache.exported_mesh_files.add(fp)
@staticmethod @staticmethod
def calc_aabb(bobject): def calc_aabb(bobject):
@ -2057,7 +2070,7 @@ class LeenkxExporter:
fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled) fp = self.get_meshes_file_path('mesh_' + oid, compressed=LeenkxExporter.compress_enabled)
assets.add(fp) assets.add(fp)
# No export necessary # 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 return
# Mesh users have different modifier stack # Mesh users have different modifier stack
@ -2227,6 +2240,7 @@ class LeenkxExporter:
inner_angle = math.atan(math.tan(half_angle) * (1.0 - blend)) inner_angle = math.atan(math.tan(half_angle) * (1.0 - blend))
out_light['spot_size'] = outer_cos out_light['spot_size'] = outer_cos
out_light['spot_blend'] = max(0.0001, math.cos(inner_angle) - 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: if light_ref.shadow_soft_size > 0.0:
out_light['light_size'] = light_ref.shadow_soft_size * 10 out_light['light_size'] = light_ref.shadow_soft_size * 10
elif objtype == 'AREA': elif objtype == 'AREA':
@ -2282,7 +2296,6 @@ class LeenkxExporter:
# outside the collection, then instantiate the full object # outside the collection, then instantiate the full object
# child tree if the collection gets spawned as a whole # child tree if the collection gets spawned as a whole
if bobject.parent is None or bobject.parent.name not in collection.objects: 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': if bobject.type == 'CAMERA':
asset_name = bobject.name + '_' + (os.path.basename(self.scene.library.filepath) if self.scene.library else self.scene.name) asset_name = bobject.name + '_' + (os.path.basename(self.scene.library.filepath) if self.scene.library else self.scene.name)
else: else:
@ -2925,7 +2938,7 @@ class LeenkxExporter:
if collection.name.startswith(('RigidBodyWorld', 'Trait|')): if collection.name.startswith(('RigidBodyWorld', 'Trait|')):
continue 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: if collection not in self.inlined_collections:
self.export_collection(collection) self.export_collection(collection)

View File

@ -19,6 +19,7 @@ import webbrowser
import bpy import bpy
from lnx import assets from lnx import assets
from lnx.exporter import BuildExportCache
from lnx.exporter import LeenkxExporter from lnx.exporter import LeenkxExporter
import lnx.lib.make_datas import lnx.lib.make_datas
import lnx.lib.server import lnx.lib.server
@ -265,19 +266,21 @@ def export_data_impl(fp, sdk_path):
continue continue
for o in scene.collection.all_objects: for o in scene.collection.all_objects:
if o.type in ('MESH', 'EMPTY'): 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.objects.link(o)
export_coll_names.add(o.name) export_coll_names.add(o.name)
depsgraph = bpy.context.evaluated_depsgraph_get() depsgraph = bpy.context.evaluated_depsgraph_get()
bpy.data.collections.remove(export_coll) # Destroy the "zoo" collection bpy.data.collections.remove(export_coll) # Destroy the "zoo" collection
build_cache = BuildExportCache()
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
if scene.lnx_export: if scene.lnx_export:
# Reset shader comparison arrays to prevent cross-scene shader merging # Reset shader comparison arrays to prevent cross-scene shader merging
assets.reset_shader_cons() assets.reset_shader_cons()
ext = '.lz4' if LeenkxExporter.compress_enabled else '.lnx' 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 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: if LeenkxExporter.export_physics:
physics_found = True physics_found = True
if LeenkxExporter.export_navigation: if LeenkxExporter.export_navigation: