Add Support for Geometry Node Cache #92890

Open
Jonas Dichelle wants to merge 14 commits from JonasDichelle/blender-asset-tracer:geonodes_support into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
6 changed files with 95 additions and 2 deletions

2
.gitignore vendored
View File

@ -10,4 +10,4 @@ __pycache__
/dist/ /dist/
/build/ /build/
/docs/_build /docs/_build

View File

@ -20,6 +20,7 @@
# (c) 2014, Blender Foundation - Campbell Barton # (c) 2014, Blender Foundation - Campbell Barton
# (c) 2018, Blender Foundation - Sybren A. Stüvel # (c) 2018, Blender Foundation - Sybren A. Stüvel
import typing import typing
import copy
from blender_asset_tracer import cdefs from blender_asset_tracer import cdefs
from . import BlendFileBlock from . import BlendFileBlock
@ -70,3 +71,24 @@ def modifiers(object_block: BlendFileBlock) -> typing.Iterator[BlendFileBlock]:
# 'ob->modifiers[...]' # 'ob->modifiers[...]'
mods = object_block.get_pointer((b"modifiers", b"first")) mods = object_block.get_pointer((b"modifiers", b"first"))
yield from listbase(mods, next_path=(b"modifier", b"next")) yield from listbase(mods, next_path=(b"modifier", b"next"))
JonasDichelle marked this conversation as resolved

I think it's fine to make this function a method on BlendFileBlock. It could then simply be named .clone() or .copy().

Then again, is there any reason to not use copy.copy() and avoid the need for this function altogether?

I think it's fine to make this function a method on `BlendFileBlock`. It could then simply be named `.clone()` or `.copy()`. Then again, is there any reason to not use [copy.copy()](https://docs.python.org/3/library/copy.html#copy.copy) and avoid the need for this function altogether?
Review

yes copy should work fine for this too.

yes copy should work fine for this too.
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)

Please add a bit more explanation to this documentation. This first line is fine, but then I would love to see some explanation of what a "dynamic array" is in this context.

Please add a bit more explanation to this documentation. This first line is fine, but then I would love to see some explanation of what a "dynamic array" is in this context.
new_block.file_offset = offset
new_block.size = element_size
yield new_block
offset += element_size
JonasDichelle marked this conversation as resolved Outdated

Instead of making Python do the block.dna_type.size lookup twice per loop, do that once outside the loop and store the value in a local variable.

block_size = block.dna_type.size // block.count
Instead of making Python do the `block.dna_type.size` lookup twice per loop, do that once outside the loop and store the value in a local variable. ``` block_size = block.dna_type.size // block.count ```

View File

@ -52,6 +52,9 @@ eModifierType_MeshSequenceCache = 52
eModifierType_Fluid = 56 eModifierType_Fluid = 56
eModifierType_Nodes = 57 eModifierType_Nodes = 57
# NodesModifierBakeFlag
NODES_MODIFIER_BAKE_CUSTOM_PATH = 1 << 1
# DNA_particle_types.h # DNA_particle_types.h
PART_DRAW_OB = 7 PART_DRAW_OB = 7
PART_DRAW_GR = 8 PART_DRAW_GR = 8
@ -101,4 +104,4 @@ PTCACHE_PATH = b"blendcache_"
# BKE_node.h # BKE_node.h
SH_NODE_TEX_IMAGE = 143 SH_NODE_TEX_IMAGE = 143
CMP_NODE_R_LAYERS = 221 CMP_NODE_R_LAYERS = 221

View File

@ -351,3 +351,48 @@ def modifier_dynamic_paint(
yield from _walk_point_cache( yield from _walk_point_cache(
ctx, surface_block_name, modifier.bfile, point_cache, cdefs.PTCACHE_EXT 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:
JonasDichelle marked this conversation as resolved

Please don't use string operations to get a single bit flag. Add a constant to cdefs.py with the name of the flag, then use something like:

use_custom_path = bool(flag & cdefs.NODES_MODIFIER_BAKE_CUSTOM_PATH)
Please don't use string operations to get a single bit flag. Add a constant to `cdefs.py` with the name of the flag, then use something like: ```python use_custom_path = bool(flag & cdefs.NODES_MODIFIER_BAKE_CUSTOM_PATH) ```
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)
JonasDichelle marked this conversation as resolved

I think it's slightly nicer to not compare to a concrete value, and just use if not directory_ptr:

I think it's slightly nicer to not compare to a concrete value, and just use `if not 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,
)

Binary file not shown.

View File

@ -279,6 +279,29 @@ class ArrayTest(AbstractBlendFileTest):
self.assertEqual(name, tex.id_name) 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): class CompressionRecognitionTest(AbstractBlendFileTest):
def _find_compression_type(self, filename: str) -> magic_compression.Compression: def _find_compression_type(self, filename: str) -> magic_compression.Compression:
path = self.blendfiles / filename path = self.blendfiles / filename