diff --git a/.gitignore b/.gitignore index 7f0097a..07b1e33 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ __pycache__ /dist/ /build/ -/docs/_build +/docs/_build \ No newline at end of file diff --git a/blender_asset_tracer/blendfile/iterators.py b/blender_asset_tracer/blendfile/iterators.py index 21849a6..b587f1c 100644 --- a/blender_asset_tracer/blendfile/iterators.py +++ b/blender_asset_tracer/blendfile/iterators.py @@ -20,6 +20,7 @@ # (c) 2014, Blender Foundation - Campbell Barton # (c) 2018, Blender Foundation - Sybren A. Stüvel import typing +import copy from blender_asset_tracer import cdefs from . import BlendFileBlock @@ -70,3 +71,24 @@ def modifiers(object_block: BlendFileBlock) -> typing.Iterator[BlendFileBlock]: # 'ob->modifiers[...]' mods = object_block.get_pointer((b"modifiers", b"first")) yield from listbase(mods, next_path=(b"modifier", b"next")) + + +def dynamic_array(block: BlendFileBlock) -> typing.Iterator[BlendFileBlock]: + """ + Generator that yields each element of a dynamic array as a separate block. + + Dynamic arrays are multiple contiguous elements accessed via a single pointer. + BAT interprets these as a single data block, making it hard to access individual elements. + This function divides the array into individual blocks by creating modified copies of the original block. + """ + + offset = block.file_offset + element_size = block.dna_type.size + + for i in range(block.count): + new_block = copy.copy(block) + new_block.file_offset = offset + new_block.size = element_size + + yield new_block + offset += element_size diff --git a/blender_asset_tracer/cdefs.py b/blender_asset_tracer/cdefs.py index c5b7fd1..72d03c0 100644 --- a/blender_asset_tracer/cdefs.py +++ b/blender_asset_tracer/cdefs.py @@ -52,6 +52,9 @@ eModifierType_MeshSequenceCache = 52 eModifierType_Fluid = 56 eModifierType_Nodes = 57 +# NodesModifierBakeFlag +NODES_MODIFIER_BAKE_CUSTOM_PATH = 1 << 1 + # DNA_particle_types.h PART_DRAW_OB = 7 PART_DRAW_GR = 8 @@ -101,4 +104,4 @@ PTCACHE_PATH = b"blendcache_" # BKE_node.h SH_NODE_TEX_IMAGE = 143 -CMP_NODE_R_LAYERS = 221 +CMP_NODE_R_LAYERS = 221 \ No newline at end of file diff --git a/blender_asset_tracer/trace/modifier_walkers.py b/blender_asset_tracer/trace/modifier_walkers.py index 597c287..3f1f701 100644 --- a/blender_asset_tracer/trace/modifier_walkers.py +++ b/blender_asset_tracer/trace/modifier_walkers.py @@ -351,3 +351,48 @@ def modifier_dynamic_paint( yield from _walk_point_cache( ctx, surface_block_name, modifier.bfile, point_cache, cdefs.PTCACHE_EXT ) + + +@mod_handler(cdefs.eModifierType_Nodes) +def modifier_nodes( + ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes +) -> typing.Iterator[result.BlockUsage]: + mod_directory_ptr, mod_directory_field = modifier.get( + b"simulation_bake_directory", return_field=True + ) + + bakes = modifier.get_pointer(b"bakes") + + for bake_idx, bake in enumerate(blendfile.iterators.dynamic_array(bakes)): + bake_directory_ptr, bake_directory_field = bake.get( + b"directory", return_field=True + ) + + flag = bake.get(b"flag") + use_custom_directory = bool(flag & cdefs.NODES_MODIFIER_BAKE_CUSTOM_PATH) + + if use_custom_directory: + directory_ptr = bake_directory_ptr + field = bake_directory_field + block = bake + else: + directory_ptr = mod_directory_ptr + field = mod_directory_field + block = modifier + + if not directory_ptr: + continue + directory = bake.bfile.dereference_pointer(directory_ptr) + if not directory: + continue + + bpath = bytes(directory.as_string(), "utf-8") + bake_block_name = block_name + b".bakes[%d]" % bake_idx + + yield result.BlockUsage( + block, + bpath, + block_name=bake_block_name, + path_full_field=field, + is_sequence=True, + ) diff --git a/tests/blendfiles/multiple_geometry_nodes_bakes.blend b/tests/blendfiles/multiple_geometry_nodes_bakes.blend new file mode 100644 index 0000000..cb977c1 Binary files /dev/null and b/tests/blendfiles/multiple_geometry_nodes_bakes.blend differ diff --git a/tests/test_blendfile_loading.py b/tests/test_blendfile_loading.py index 914c071..1e71ab8 100644 --- a/tests/test_blendfile_loading.py +++ b/tests/test_blendfile_loading.py @@ -279,6 +279,29 @@ class ArrayTest(AbstractBlendFileTest): self.assertEqual(name, tex.id_name) +class DynamicArrayTest(AbstractBlendFileTest): + def test_dynamic_array_of_bakes(self): + self.bf = blendfile.BlendFile(self.blendfiles / "multiple_geometry_nodes_bakes.blend") + obj = self.bf.code_index[b"OB"][0] + assert isinstance(obj, blendfile.BlendFileBlock) + modifier = obj.get_pointer((b"modifiers", b"first")) + assert isinstance(modifier, blendfile.BlendFileBlock) + bakes = modifier.get_pointer(b"bakes") + + bake_count = bakes.count + self.assertEqual(3, bake_count) + + for i, bake in enumerate(blendfile.iterators.dynamic_array(bakes)): + if i == 0: + frame_start = 37 + if i == 1: + frame_start = 5 + if i == 2: + frame_start = 12 + + self.assertEqual(frame_start, bake.get(b"frame_start")) + + class CompressionRecognitionTest(AbstractBlendFileTest): def _find_compression_type(self, filename: str) -> magic_compression.Compression: path = self.blendfiles / filename