Add Support for Geometry Node Cache #92890
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,4 +10,4 @@ __pycache__
|
||||
/dist/
|
||||
/build/
|
||||
|
||||
/docs/_build
|
||||
/docs/_build
|
@ -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"))
|
||||
|
||||
|
||||
JonasDichelle marked this conversation as resolved
|
||||
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)
|
||||
Sybren A. Stüvel
commented
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
Sybren A. Stüvel
commented
Instead of making Python do the
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
```
|
||||
|
@ -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
|
@ -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:
|
||||
JonasDichelle marked this conversation as resolved
Sybren A. Stüvel
commented
Please don't use string operations to get a single bit flag. Add a constant to
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
Sybren A. Stüvel
commented
I think it's slightly nicer to not compare to a concrete value, and just use 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,
|
||||
)
|
||||
|
BIN
tests/blendfiles/multiple_geometry_nodes_bakes.blend
Normal file
BIN
tests/blendfiles/multiple_geometry_nodes_bakes.blend
Normal file
Binary file not shown.
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user
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?
yes copy should work fine for this too.