Files
LNXSDK/leenkx/blender/lnx/linked_utils.py

145 lines
5.0 KiB
Python

"""Utilities for handling linked blend files in Leenkx exports."""
from contextlib import contextmanager
from pathlib import Path
from typing import Dict, Optional
import bpy
import lnx
if lnx.is_reload(__name__):
pass
else:
lnx.enable_reload(__name__)
def is_linked(bdata) -> bool:
"""Check if a data block is linked from an external library."""
if bdata is None:
return False
return bdata.library is not None
def get_library_name(bdata) -> Optional[str]:
"""Get the library filename for a linked data block."""
if bdata is None or bdata.library is None:
return None
return bdata.library.name
def get_library_path(bdata) -> Optional[Path]:
"""Get the absolute path to the library .blend file."""
if bdata is None or bdata.library is None:
return None
return Path(bpy.path.abspath(bdata.library.filepath))
def asset_name(bdata) -> Optional[str]:
"""Get qualified asset name with library suffix for linked data."""
if bdata is None:
return None
name = bdata.name
if bdata.library is not None:
name += '_' + bdata.library.name
return name
def get_source_path(bdata) -> Optional[Path]:
"""Get Sources folder path for a linked data block's project."""
lib_path = get_library_path(bdata)
if lib_path is None:
return None
sources_path = lib_path.parent / 'Sources'
if sources_path.exists() and sources_path.is_dir():
return sources_path
return None
class TransformEvaluator:
"""Context manager for evaluating linked object transforms (Blender 4.2+ workaround)."""
def __init__(self, bobject: bpy.types.Object, scene: bpy.types.Scene,
depsgraph: bpy.types.Depsgraph):
self.bobject = bobject
self.scene = scene
self.depsgraph = depsgraph
self._temp_collection: Optional[bpy.types.Collection] = None
self._evaluated_obj: Optional[bpy.types.Object] = None
self._is_linked = False
def __enter__(self) -> 'TransformEvaluator':
if bpy.app.version >= (4, 2, 0):
self._is_linked = self.bobject.name not in self.scene.collection.children
if self._is_linked:
self._temp_collection = bpy.data.collections.new("_lnx_temp_eval")
bpy.context.scene.collection.children.link(self._temp_collection)
self._temp_collection.objects.link(self.bobject)
temp_depsgraph = bpy.context.evaluated_depsgraph_get()
self._evaluated_obj = self.bobject.evaluated_get(temp_depsgraph)
else:
self._evaluated_obj = self.bobject.evaluated_get(self.depsgraph)
else:
self._evaluated_obj = self.bobject
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._is_linked and self._temp_collection is not None:
try:
self._temp_collection.objects.unlink(self.bobject)
bpy.context.scene.collection.children.unlink(self._temp_collection)
bpy.data.collections.remove(self._temp_collection)
except Exception:
pass
self._temp_collection = None
return False
@property
def evaluated_object(self) -> bpy.types.Object:
return self._evaluated_obj
@property
def matrix_local(self):
if bpy.app.version >= (4, 2, 0):
return self._evaluated_obj.matrix_local.copy()
return self.bobject.matrix_local
@contextmanager
def evaluated_mesh(bobject: bpy.types.Object, scene: bpy.types.Scene,
depsgraph: bpy.types.Depsgraph, apply_modifiers: bool = True):
"""Context manager for mesh export with Blender 4.2+ linked object workaround."""
temp_collection = None
is_linked = False
try:
if apply_modifiers and bpy.app.version >= (4, 2, 0):
is_linked = bobject.name not in scene.collection.children
if is_linked:
temp_collection = bpy.data.collections.new("_lnx_temp_mesh_eval")
bpy.context.scene.collection.children.link(temp_collection)
temp_collection.objects.link(bobject)
temp_depsgraph = bpy.context.evaluated_depsgraph_get()
bobject_eval = bobject.evaluated_get(temp_depsgraph)
yield bobject_eval, temp_depsgraph
finally:
if is_linked and temp_collection is not None:
try:
temp_collection.objects.unlink(bobject)
bpy.context.scene.collection.children.unlink(temp_collection)
bpy.data.collections.remove(temp_collection)
except Exception:
pass
def discover_linked_sources() -> Dict[str, Path]:
"""Discover Sources folders from all linked libraries."""
sources: Dict[str, Path] = {}
for lib in bpy.data.libraries:
lib_path = Path(bpy.path.abspath(lib.filepath))
sources_path = lib_path.parent / 'Sources'
if sources_path.exists() and sources_path.is_dir():
sources[lib.name] = sources_path
return sources