Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4085f0015f | |||
| cf31dbc5ec | |||
| bb1de717c8 | |||
| 12e6e2643b | |||
| cbbe8be0d8 | |||
|
|
6af9567a44 | ||
|
|
74860b3107 | ||
|
|
5c0edf4c07 | ||
|
|
a973637896 | ||
|
|
7322c0772a | ||
|
|
3b61399f7f | ||
|
|
aceea0d823 | ||
|
|
2c0fe87fba | ||
|
|
47c3e8d77f | ||
|
|
c69612b264 | ||
|
|
b1c4f5e116 | ||
|
|
5690c8e7cb | ||
|
|
50578ac62a | ||
|
|
94486a3218 | ||
|
|
95165a0330 | ||
|
|
8a17495566 |
13
CHANGELOG.md
13
CHANGELOG.md
@ -3,7 +3,18 @@
|
|||||||
This file logs the changes that are actually interesting to users (new features,
|
This file logs the changes that are actually interesting to users (new features,
|
||||||
changed functionality, fixed bugs).
|
changed functionality, fixed bugs).
|
||||||
|
|
||||||
# Version 1.20 (in development)
|
# Version 1.21 (2025-11-24)
|
||||||
|
|
||||||
|
- Require Python version 3.11 or newer. Versions up to Python 3.14 are supported.
|
||||||
|
- Skip packed blend files. BAT will assume that the packed file is self-contained, i.e. any asset used by a packed blend file should also be packed.
|
||||||
|
- Support Geometry Node simulation caches ([#92890](https://projects.blender.org/blender/blender-asset-tracer/pulls/92890)).
|
||||||
|
- Add a `py.typed` marker file, such that tooling like mypy know BAT has type annotations ([#92895](https://projects.blender.org/blender/blender-asset-tracer/pulls/92895)).
|
||||||
|
- Add support for getting individual array items ([#92898](https://projects.blender.org/blender/blender-asset-tracer/pulls/92898)) and iterating dynamic arrays (part of [#92890](https://projects.blender.org/blender/blender-asset-tracer/pulls/92890)).
|
||||||
|
- Support setting sub-properties via `block.field_set()` ([#92899](https://projects.blender.org/blender/blender-asset-tracer/pulls/92899)).
|
||||||
|
- Make `bat blocks` print the biggest block memory address as hexadecimal ([#92900](https://projects.blender.org/blender/blender-asset-tracer/pulls/92900)).
|
||||||
|
|
||||||
|
|
||||||
|
# Version 1.20 (2025-07-11)
|
||||||
|
|
||||||
- Add support for Blender 5.0 compositor node trees ([16c208bc8e13](https://projects.blender.org/blender/blender-asset-tracer/commit/16c208bc8e130c8b1233bdb411ecabdab19af3c5)).
|
- Add support for Blender 5.0 compositor node trees ([16c208bc8e13](https://projects.blender.org/blender/blender-asset-tracer/commit/16c208bc8e130c8b1233bdb411ecabdab19af3c5)).
|
||||||
- Make it possible to run BAT with `python -m blender_asset_tracer` ([6c42d06f0590](https://projects.blender.org/blender/blender-asset-tracer/commit/6c42d06f05909d4ac2096e84557d19dd93382f3a)).
|
- Make it possible to run BAT with `python -m blender_asset_tracer` ([6c42d06f0590](https://projects.blender.org/blender/blender-asset-tracer/commit/6c42d06f05909d4ac2096e84557d19dd93382f3a)).
|
||||||
|
|||||||
78
README.md
78
README.md
@ -1,4 +1,4 @@
|
|||||||
# Blender Asset Tracer BAT🦇
|
# Blender Asset Tracer BAT🦇 (ADV fork)
|
||||||
|
|
||||||
Script to manage assets with Blender.
|
Script to manage assets with Blender.
|
||||||
|
|
||||||
@ -75,49 +75,51 @@ Mypy likes to see the return type of `__init__` methods explicitly declared as `
|
|||||||
BAT can be used as a Python library to inspect the contents of blend files, without having to
|
BAT can be used as a Python library to inspect the contents of blend files, without having to
|
||||||
open Blender itself. Here is an example showing how to determine the render engine used:
|
open Blender itself. Here is an example showing how to determine the render engine used:
|
||||||
|
|
||||||
#!/usr/bin/env python3.7
|
```python
|
||||||
import json
|
#!/usr/bin/env python3.7
|
||||||
import sys
|
import json
|
||||||
from pathlib import Path
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from blender_asset_tracer import blendfile
|
from blender_asset_tracer import blendfile
|
||||||
from blender_asset_tracer.blendfile import iterators
|
from blender_asset_tracer.blendfile import iterators
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
if len(sys.argv) != 2:
|
||||||
print(f'Usage: {sys.argv[0]} somefile.blend', file=sys.stderr)
|
print(f'Usage: {sys.argv[0]} somefile.blend', file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
bf_path = Path(sys.argv[1])
|
bf_path = Path(sys.argv[1])
|
||||||
bf = blendfile.open_cached(bf_path)
|
bf = blendfile.open_cached(bf_path)
|
||||||
|
|
||||||
# Get the first window manager (there is probably exactly one).
|
# Get the first window manager (there is probably exactly one).
|
||||||
window_managers = bf.find_blocks_from_code(b'WM')
|
window_managers = bf.find_blocks_from_code(b'WM')
|
||||||
assert window_managers, 'The Blend file has no window manager'
|
assert window_managers, 'The Blend file has no window manager'
|
||||||
window_manager = window_managers[0]
|
window_manager = window_managers[0]
|
||||||
|
|
||||||
# Get the scene from the first window.
|
# Get the scene from the first window.
|
||||||
windows = window_manager.get_pointer((b'windows', b'first'))
|
windows = window_manager.get_pointer((b'windows', b'first'))
|
||||||
for window in iterators.listbase(windows):
|
for window in iterators.listbase(windows):
|
||||||
scene = window.get_pointer(b'scene')
|
scene = window.get_pointer(b'scene')
|
||||||
break
|
break
|
||||||
|
|
||||||
# BAT can only return simple values, so it can't return the embedded
|
# BAT can only return simple values, so it can't return the embedded
|
||||||
# struct 'r'. 'r.engine' is a simple string, though.
|
# struct 'r'. 'r.engine' is a simple string, though.
|
||||||
engine = scene[b'r', b'engine'].decode('utf8')
|
engine = scene[b'r', b'engine'].decode('utf8')
|
||||||
xsch = scene[b'r', b'xsch']
|
xsch = scene[b'r', b'xsch']
|
||||||
ysch = scene[b'r', b'ysch']
|
ysch = scene[b'r', b'ysch']
|
||||||
size = scene[b'r', b'size'] / 100.0
|
size = scene[b'r', b'size'] / 100.0
|
||||||
|
|
||||||
render_info = {
|
render_info = {
|
||||||
'engine': engine,
|
'engine': engine,
|
||||||
'frame_pixels': {
|
'frame_pixels': {
|
||||||
'x': int(xsch * size),
|
'x': int(xsch * size),
|
||||||
'y': int(ysch * size),
|
'y': int(ysch * size),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
json.dump(render_info, sys.stdout, indent=4, sort_keys=True)
|
json.dump(render_info, sys.stdout, indent=4, sort_keys=True)
|
||||||
print()
|
print()
|
||||||
|
```
|
||||||
|
|
||||||
To understand the naming of the properties, look at Blender's `DNA_xxxx.h` files with struct
|
To understand the naming of the properties, look at Blender's `DNA_xxxx.h` files with struct
|
||||||
definitions. It is those names that are accessed via `blender_asset_tracer.blendfile`. The
|
definitions. It is those names that are accessed via `blender_asset_tracer.blendfile`. The
|
||||||
@ -171,6 +173,6 @@ index-servers =
|
|||||||
pip install twine
|
pip install twine
|
||||||
|
|
||||||
poetry build
|
poetry build
|
||||||
poetry run twine check dist/blender_asset_tracer-1.19.tar.gz dist/blender_asset_tracer-1.19-*.whl
|
poetry run twine check dist/blender_asset_tracer-1.21.tar.gz dist/blender_asset_tracer-1.21-*.whl
|
||||||
poetry run twine upload -r bat dist/blender_asset_tracer-1.19.tar.gz dist/blender_asset_tracer-1.19-*.whl
|
poetry run twine upload -r bat dist/blender_asset_tracer-1.21.tar.gz dist/blender_asset_tracer-1.21-*.whl
|
||||||
```
|
```
|
||||||
|
|||||||
@ -20,4 +20,57 @@
|
|||||||
|
|
||||||
# <pep8 compliant>
|
# <pep8 compliant>
|
||||||
|
|
||||||
__version__ = "1.19"
|
__version__ = "1.21"
|
||||||
|
|
||||||
|
bl_info = {
|
||||||
|
"name": "Blender Asset Tracer",
|
||||||
|
"author": "Campbell Barton, Sybren A. St\u00fcvel, Lo\u00efc Charri\u00e8re, Cl\u00e9ment Ducarteron, Mario Hawat, Joseph Henry",
|
||||||
|
"version": (1, 21, 0),
|
||||||
|
"blender": (2, 80, 0),
|
||||||
|
"location": "File > External Data > BAT",
|
||||||
|
"description": "Utility for packing blend files",
|
||||||
|
"warning": "",
|
||||||
|
"wiki_url": "https://developer.blender.org/project/profile/79/",
|
||||||
|
"category": "Import-Export",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Reset root module name if folder has an unexpected name
|
||||||
|
# (like "blender_asset_tracer-main" from zip-dl)
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if __name__ != "blender_asset_tracer":
|
||||||
|
sys.modules["blender_asset_tracer"] = sys.modules[__name__]
|
||||||
|
|
||||||
|
try:
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
_HAS_BPY = True
|
||||||
|
except ImportError:
|
||||||
|
_HAS_BPY = False
|
||||||
|
|
||||||
|
if _HAS_BPY:
|
||||||
|
from blender_asset_tracer import blendfile
|
||||||
|
from . import preferences, operators
|
||||||
|
|
||||||
|
# Match the CLI's default: skip dangling pointers gracefully instead of crashing.
|
||||||
|
# Production blend files often have references to missing linked libraries.
|
||||||
|
blendfile.set_strict_pointer_mode(False)
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
preferences.BATPreferences,
|
||||||
|
operators.ExportBatPack,
|
||||||
|
operators.BAT_OT_export_zip,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
bpy.types.TOPBAR_MT_file_external_data.append(operators.menu_func)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
bpy.types.TOPBAR_MT_file_external_data.remove(operators.menu_func)
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.unregister_class(cls)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
register()
|
||||||
|
|||||||
@ -447,6 +447,12 @@ class BlendFileBlock:
|
|||||||
old_structure = struct.Struct(b"4sI")
|
old_structure = struct.Struct(b"4sI")
|
||||||
"""old blend files ENDB block structure"""
|
"""old blend files ENDB block structure"""
|
||||||
|
|
||||||
|
# Explicitly annotate to avoid `Any` from `.unpack()`.
|
||||||
|
size: int
|
||||||
|
addr_old: int
|
||||||
|
sdna_index: int
|
||||||
|
count: int
|
||||||
|
|
||||||
def __init__(self, bfile: BlendFile) -> None:
|
def __init__(self, bfile: BlendFile) -> None:
|
||||||
self.bfile = bfile
|
self.bfile = bfile
|
||||||
|
|
||||||
@ -590,6 +596,7 @@ class BlendFileBlock:
|
|||||||
null_terminated=True,
|
null_terminated=True,
|
||||||
as_str=False,
|
as_str=False,
|
||||||
return_field=False,
|
return_field=False,
|
||||||
|
array_index=0,
|
||||||
) -> typing.Any:
|
) -> typing.Any:
|
||||||
"""Read a property and return the value.
|
"""Read a property and return the value.
|
||||||
|
|
||||||
@ -606,8 +613,20 @@ class BlendFileBlock:
|
|||||||
(assumes UTF-8 encoding).
|
(assumes UTF-8 encoding).
|
||||||
:param return_field: When True, returns tuple (dna.Field, value).
|
:param return_field: When True, returns tuple (dna.Field, value).
|
||||||
Otherwise just returns the value.
|
Otherwise just returns the value.
|
||||||
|
:param array_index: If the property is an array, this determines the
|
||||||
|
index of the returned item from that array. Also see
|
||||||
|
`blendfile.iterators.dynamic_array()` for iterating such arrays.
|
||||||
"""
|
"""
|
||||||
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
|
file_offset = self.file_offset
|
||||||
|
if array_index:
|
||||||
|
if not (0 <= array_index < self.count):
|
||||||
|
raise IndexError(
|
||||||
|
"Invalid 'array_index' for file-block. "
|
||||||
|
f"Expected int value in range 0-{self.count - 1}, got {array_index}."
|
||||||
|
)
|
||||||
|
file_offset += array_index * self.dna_type.size
|
||||||
|
|
||||||
|
self.bfile.fileobj.seek(file_offset, os.SEEK_SET)
|
||||||
|
|
||||||
dna_struct = self.bfile.structs[self.sdna_index]
|
dna_struct = self.bfile.structs[self.sdna_index]
|
||||||
field, value = dna_struct.field_get(
|
field, value = dna_struct.field_get(
|
||||||
@ -627,8 +646,8 @@ class BlendFileBlock:
|
|||||||
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
|
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
|
||||||
return self.bfile.fileobj.read(self.size)
|
return self.bfile.fileobj.read(self.size)
|
||||||
|
|
||||||
def as_string(self) -> str:
|
def as_bytes_string(self) -> bytes:
|
||||||
"""Interpret the bytes of this datablock as null-terminated utf8 string."""
|
"""Interpret the bytes of this datablock as null-terminated string of raw bytes."""
|
||||||
the_bytes = self.raw_data()
|
the_bytes = self.raw_data()
|
||||||
try:
|
try:
|
||||||
first_null = the_bytes.index(0)
|
first_null = the_bytes.index(0)
|
||||||
@ -636,6 +655,11 @@ class BlendFileBlock:
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
the_bytes = the_bytes[:first_null]
|
the_bytes = the_bytes[:first_null]
|
||||||
|
return the_bytes
|
||||||
|
|
||||||
|
def as_string(self) -> str:
|
||||||
|
"""Interpret the bytes of this datablock as null-terminated utf8 string."""
|
||||||
|
the_bytes = self.as_bytes_string()
|
||||||
return the_bytes.decode()
|
return the_bytes.decode()
|
||||||
|
|
||||||
def get_recursive_iter(
|
def get_recursive_iter(
|
||||||
@ -702,7 +726,7 @@ class BlendFileBlock:
|
|||||||
hsh = zlib.adler32(str(value).encode(), hsh)
|
hsh = zlib.adler32(str(value).encode(), hsh)
|
||||||
return hsh
|
return hsh
|
||||||
|
|
||||||
def set(self, path: bytes, value):
|
def set(self, path: dna.FieldPath, value):
|
||||||
dna_struct = self.bfile.structs[self.sdna_index]
|
dna_struct = self.bfile.structs[self.sdna_index]
|
||||||
self.bfile.mark_modified()
|
self.bfile.mark_modified()
|
||||||
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
|
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
|
||||||
@ -811,9 +835,13 @@ class BlendFileBlock:
|
|||||||
def __getitem__(self, path: dna.FieldPath):
|
def __getitem__(self, path: dna.FieldPath):
|
||||||
return self.get(path)
|
return self.get(path)
|
||||||
|
|
||||||
def __setitem__(self, item: bytes, value) -> None:
|
def __setitem__(self, item: dna.FieldPath, value) -> None:
|
||||||
self.set(item, value)
|
self.set(item, value)
|
||||||
|
|
||||||
|
def has_field(self, name: bytes) -> bool:
|
||||||
|
dna_struct = self.bfile.structs[self.sdna_index]
|
||||||
|
return dna_struct.has_field(name)
|
||||||
|
|
||||||
def keys(self) -> typing.Iterator[bytes]:
|
def keys(self) -> typing.Iterator[bytes]:
|
||||||
"""Generator, yields all field names of this block."""
|
"""Generator, yields all field names of this block."""
|
||||||
return (f.name.name_only for f in self.dna_type.fields)
|
return (f.name.name_only for f in self.dna_type.fields)
|
||||||
|
|||||||
@ -310,7 +310,7 @@ class Struct:
|
|||||||
self,
|
self,
|
||||||
file_header: header.BlendFileHeader,
|
file_header: header.BlendFileHeader,
|
||||||
fileobj: typing.IO[bytes],
|
fileobj: typing.IO[bytes],
|
||||||
path: bytes,
|
path: FieldPath,
|
||||||
value: typing.Any,
|
value: typing.Any,
|
||||||
):
|
):
|
||||||
"""Write a value to the blend file.
|
"""Write a value to the blend file.
|
||||||
@ -319,7 +319,6 @@ class Struct:
|
|||||||
struct on disk (e.g. the start of the BlendFileBlock containing the
|
struct on disk (e.g. the start of the BlendFileBlock containing the
|
||||||
data).
|
data).
|
||||||
"""
|
"""
|
||||||
assert isinstance(path, bytes), "path should be bytes, but is %s" % type(path)
|
|
||||||
|
|
||||||
field, offset = self.field_from_path(file_header.pointer_size, path)
|
field, offset = self.field_from_path(file_header.pointer_size, path)
|
||||||
|
|
||||||
|
|||||||
@ -204,17 +204,17 @@ class EndianIO:
|
|||||||
return fileobj.write(to_write)
|
return fileobj.write(to_write)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def read_bytes0(cls, fileobj, length):
|
def read_bytes0(cls, fileobj: typing.IO[bytes], length: int) -> bytes:
|
||||||
data = fileobj.read(length)
|
data = fileobj.read(length)
|
||||||
return cls.read_data0(data)
|
return cls.read_data0(data)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def read_data0_offset(cls, data, offset):
|
def read_data0_offset(cls, data: bytes, offset: int) -> bytes:
|
||||||
add = data.find(b"\0", offset) - offset
|
add = data.find(b"\0", offset) - offset
|
||||||
return data[offset : offset + add]
|
return data[offset : offset + add]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def read_data0(cls, data):
|
def read_data0(cls, data: bytes) -> bytes:
|
||||||
add = data.find(b"\0")
|
add = data.find(b"\0")
|
||||||
if add < 0:
|
if add < 0:
|
||||||
return data
|
return data
|
||||||
|
|||||||
@ -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,27 @@ 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"))
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
See `some_block.get(b'name', array_index)` if you want to access elements by
|
||||||
|
index (instead of iterating).
|
||||||
|
"""
|
||||||
|
|
||||||
|
element_size = block.dna_type.size
|
||||||
|
|
||||||
|
sub_block = copy.copy(block)
|
||||||
|
sub_block.size = element_size
|
||||||
|
|
||||||
|
for i in range(block.count):
|
||||||
|
# When sub_block's data is read, it'll be read from this offset in the blend file.
|
||||||
|
sub_block.file_offset = block.file_offset + i * element_size
|
||||||
|
yield sub_block
|
||||||
|
|||||||
@ -52,6 +52,14 @@ eModifierType_MeshSequenceCache = 52
|
|||||||
eModifierType_Fluid = 56
|
eModifierType_Fluid = 56
|
||||||
eModifierType_Nodes = 57
|
eModifierType_Nodes = 57
|
||||||
|
|
||||||
|
# NodesModifierBakeFlag
|
||||||
|
NODES_MODIFIER_BAKE_CUSTOM_PATH = 1 << 1
|
||||||
|
|
||||||
|
# NodesModifierBakeTarget
|
||||||
|
NODES_MODIFIER_BAKE_TARGET_INHERIT = 0
|
||||||
|
NODES_MODIFIER_BAKE_TARGET_PACKED = 1
|
||||||
|
NODES_MODIFIER_BAKE_TARGET_DISK = 2
|
||||||
|
|
||||||
# DNA_particle_types.h
|
# DNA_particle_types.h
|
||||||
PART_DRAW_OB = 7
|
PART_DRAW_OB = 7
|
||||||
PART_DRAW_GR = 8
|
PART_DRAW_GR = 8
|
||||||
|
|||||||
@ -112,7 +112,7 @@ def cli_blocks(args):
|
|||||||
# From the blocks of the most space-using category, the biggest block.
|
# From the blocks of the most space-using category, the biggest block.
|
||||||
biggest_block = sorted(infos[0].blocks, key=lambda blck: blck.size, reverse=True)[0]
|
biggest_block = sorted(infos[0].blocks, key=lambda blck: blck.size, reverse=True)[0]
|
||||||
print(
|
print(
|
||||||
"Biggest %s block is %s at address %s"
|
"Biggest %s block is %s at address 0x%x"
|
||||||
% (
|
% (
|
||||||
block_key(biggest_block),
|
block_key(biggest_block),
|
||||||
common.humanize_bytes(biggest_block.size),
|
common.humanize_bytes(biggest_block.size),
|
||||||
|
|||||||
21
blender_asset_tracer/cli/pack.py
Normal file → Executable file
21
blender_asset_tracer/cli/pack.py
Normal file → Executable file
@ -88,6 +88,15 @@ def add_parser(subparsers):
|
|||||||
help="Only pack assets that are referred to with a relative path (e.g. "
|
help="Only pack assets that are referred to with a relative path (e.g. "
|
||||||
"starting with `//`.",
|
"starting with `//`.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--keep-hierarchy",
|
||||||
|
default=False,
|
||||||
|
action="store_true",
|
||||||
|
help="Preserve the full filesystem directory hierarchy in the pack. "
|
||||||
|
"All files (including the blend file) are placed at their absolute "
|
||||||
|
"path structure under the target directory. Paths in blend files are "
|
||||||
|
"rewritten to relative paths within this structure.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def cli_pack(args):
|
def cli_pack(args):
|
||||||
@ -119,6 +128,9 @@ def create_packer(
|
|||||||
if args.relative_only:
|
if args.relative_only:
|
||||||
raise ValueError("S3 uploader does not support the --relative-only option")
|
raise ValueError("S3 uploader does not support the --relative-only option")
|
||||||
|
|
||||||
|
if args.keep_hierarchy:
|
||||||
|
raise ValueError("S3 uploader does not support the --keep-hierarchy option")
|
||||||
|
|
||||||
packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target))
|
packer = create_s3packer(bpath, ppath, pathlib.PurePosixPath(target))
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
@ -137,6 +149,11 @@ def create_packer(
|
|||||||
"Shaman uploader does not support the --relative-only option"
|
"Shaman uploader does not support the --relative-only option"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if args.keep_hierarchy:
|
||||||
|
raise ValueError(
|
||||||
|
"Shaman uploader does not support the --keep-hierarchy option"
|
||||||
|
)
|
||||||
|
|
||||||
packer = create_shamanpacker(bpath, ppath, target)
|
packer = create_shamanpacker(bpath, ppath, target)
|
||||||
|
|
||||||
elif target.lower().endswith(".zip"):
|
elif target.lower().endswith(".zip"):
|
||||||
@ -146,7 +163,8 @@ def create_packer(
|
|||||||
raise ValueError("ZIP packer does not support on-the-fly compression")
|
raise ValueError("ZIP packer does not support on-the-fly compression")
|
||||||
|
|
||||||
packer = zipped.ZipPacker(
|
packer = zipped.ZipPacker(
|
||||||
bpath, ppath, target, noop=args.noop, relative_only=args.relative_only
|
bpath, ppath, target, noop=args.noop, relative_only=args.relative_only,
|
||||||
|
keep_hierarchy=args.keep_hierarchy,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
packer = pack.Packer(
|
packer = pack.Packer(
|
||||||
@ -156,6 +174,7 @@ def create_packer(
|
|||||||
noop=args.noop,
|
noop=args.noop,
|
||||||
compress=args.compress,
|
compress=args.compress,
|
||||||
relative_only=args.relative_only,
|
relative_only=args.relative_only,
|
||||||
|
keep_hierarchy=args.keep_hierarchy,
|
||||||
)
|
)
|
||||||
|
|
||||||
if args.exclude:
|
if args.exclude:
|
||||||
|
|||||||
163
blender_asset_tracer/operators.py
Normal file
163
blender_asset_tracer/operators.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy_extras.io_utils import ExportHelper
|
||||||
|
|
||||||
|
from blender_asset_tracer.pack import zipped
|
||||||
|
|
||||||
|
|
||||||
|
class ExportBatPack(Operator, ExportHelper):
|
||||||
|
bl_idname = "export_bat.pack"
|
||||||
|
bl_label = "BAT - Zip pack (flat)"
|
||||||
|
filename_ext = ".zip"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return bpy.data.is_saved
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
outfname = bpy.path.ensure_ext(self.filepath, ".zip")
|
||||||
|
self.report({"INFO"}, "Executing ZipPacker ...")
|
||||||
|
|
||||||
|
with zipped.ZipPacker(
|
||||||
|
Path(bpy.data.filepath),
|
||||||
|
Path(bpy.data.filepath).parent,
|
||||||
|
str(self.filepath),
|
||||||
|
) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
self.report({"INFO"}, "Packing successful!")
|
||||||
|
|
||||||
|
with zipfile.ZipFile(str(self.filepath)) as inzip:
|
||||||
|
inzip.testzip()
|
||||||
|
|
||||||
|
self.report({"INFO"}, "Written to %s" % outfname)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
def open_folder(folderpath):
|
||||||
|
"""Open the folder at the path given with cmd relative to user's OS."""
|
||||||
|
from shutil import which
|
||||||
|
|
||||||
|
my_os = sys.platform
|
||||||
|
if my_os.startswith(("linux", "freebsd")):
|
||||||
|
cmd = "xdg-open"
|
||||||
|
elif my_os.startswith("win"):
|
||||||
|
cmd = "explorer"
|
||||||
|
if not folderpath:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
cmd = "open"
|
||||||
|
|
||||||
|
if not folderpath:
|
||||||
|
return
|
||||||
|
|
||||||
|
folderpath = str(folderpath)
|
||||||
|
if os.path.isfile(folderpath):
|
||||||
|
select = False
|
||||||
|
if my_os.startswith("win"):
|
||||||
|
cmd = "explorer /select,"
|
||||||
|
select = True
|
||||||
|
elif my_os.startswith(("linux", "freebsd")):
|
||||||
|
if which("nemo"):
|
||||||
|
cmd = "nemo --no-desktop"
|
||||||
|
select = True
|
||||||
|
elif which("nautilus"):
|
||||||
|
cmd = "nautilus --no-desktop"
|
||||||
|
select = True
|
||||||
|
if not select:
|
||||||
|
folderpath = os.path.dirname(folderpath)
|
||||||
|
|
||||||
|
folderpath = os.path.normpath(folderpath)
|
||||||
|
fullcmd = cmd.split() + [folderpath]
|
||||||
|
subprocess.Popen(fullcmd)
|
||||||
|
|
||||||
|
|
||||||
|
class BAT_OT_export_zip(Operator, ExportHelper):
|
||||||
|
"""Export current blendfile with hierarchy preservation"""
|
||||||
|
|
||||||
|
bl_label = "BAT - Zip pack (keep hierarchy)"
|
||||||
|
bl_idname = "bat.export_zip"
|
||||||
|
|
||||||
|
filename_ext = ".zip"
|
||||||
|
|
||||||
|
root_dir: bpy.props.StringProperty(
|
||||||
|
name="Root",
|
||||||
|
description="Top Level Folder of your project."
|
||||||
|
"\nFor now Copy/Paste correct folder by hand if default is incorrect."
|
||||||
|
"\n!!! Everything outside won't be zipped !!!",
|
||||||
|
)
|
||||||
|
|
||||||
|
use_zip: bpy.props.BoolProperty(
|
||||||
|
name="Output as ZIP",
|
||||||
|
description="If enabled, pack into a ZIP archive. If disabled, copy to a directory.",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return bpy.data.is_saved
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
from blender_asset_tracer.pack import Packer
|
||||||
|
from blender_asset_tracer.pack.zipped import ZipPacker
|
||||||
|
|
||||||
|
bfile = Path(bpy.data.filepath)
|
||||||
|
project = Path(self.root_dir) if self.root_dir else bfile.parent
|
||||||
|
target = str(self.filepath)
|
||||||
|
|
||||||
|
if self.use_zip:
|
||||||
|
target = bpy.path.ensure_ext(target, ".zip")
|
||||||
|
packer_cls = ZipPacker
|
||||||
|
else:
|
||||||
|
if target.lower().endswith(".zip"):
|
||||||
|
target = target[:-4]
|
||||||
|
packer_cls = Packer
|
||||||
|
|
||||||
|
self.report({"INFO"}, "Packing with hierarchy...")
|
||||||
|
|
||||||
|
with packer_cls(bfile, project, target, keep_hierarchy=True) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
|
||||||
|
if self.use_zip:
|
||||||
|
with zipfile.ZipFile(target) as inzip:
|
||||||
|
inzip.testzip()
|
||||||
|
log_output = Path(tempfile.gettempdir(), "README.txt")
|
||||||
|
with open(log_output, "w") as log:
|
||||||
|
log.write("Packed with BAT (keep-hierarchy mode)")
|
||||||
|
log.write(f"\nBlend file: {bpy.data.filepath}")
|
||||||
|
with zipfile.ZipFile(target, "a") as zipObj:
|
||||||
|
zipObj.write(log_output, log_output.name)
|
||||||
|
|
||||||
|
self.report({"INFO"}, "Written to %s" % target)
|
||||||
|
open_folder(Path(target).parent)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
def menu_func(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.separator()
|
||||||
|
layout.operator(ExportBatPack.bl_idname)
|
||||||
|
filepath = layout.operator(BAT_OT_export_zip.bl_idname)
|
||||||
|
|
||||||
|
try:
|
||||||
|
prefs = bpy.context.preferences.addons["blender_asset_tracer"].preferences
|
||||||
|
root_dir_env = None
|
||||||
|
if prefs.use_env_root:
|
||||||
|
root_dir_env = os.getenv("ZIP_ROOT")
|
||||||
|
if not root_dir_env:
|
||||||
|
root_dir_env = os.getenv("PROJECT_ROOT")
|
||||||
|
if not root_dir_env:
|
||||||
|
root_dir_env = prefs.root_default
|
||||||
|
filepath.root_dir = "" if root_dir_env is None else root_dir_env
|
||||||
|
except Exception:
|
||||||
|
filepath.root_dir = os.getenv("ZIP_ROOT") or os.getenv("PROJECT_ROOT") or ""
|
||||||
|
|
||||||
|
|
||||||
21
blender_asset_tracer/pack/__init__.py
Normal file → Executable file
21
blender_asset_tracer/pack/__init__.py
Normal file → Executable file
@ -103,6 +103,7 @@ class Packer:
|
|||||||
noop=False,
|
noop=False,
|
||||||
compress=False,
|
compress=False,
|
||||||
relative_only=False,
|
relative_only=False,
|
||||||
|
keep_hierarchy=False,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.blendfile = bfile
|
self.blendfile = bfile
|
||||||
self.project = project
|
self.project = project
|
||||||
@ -111,6 +112,7 @@ class Packer:
|
|||||||
self.noop = noop
|
self.noop = noop
|
||||||
self.compress = compress
|
self.compress = compress
|
||||||
self.relative_only = relative_only
|
self.relative_only = relative_only
|
||||||
|
self.keep_hierarchy = keep_hierarchy
|
||||||
self._aborted = threading.Event()
|
self._aborted = threading.Event()
|
||||||
self._abort_lock = threading.RLock()
|
self._abort_lock = threading.RLock()
|
||||||
self._abort_reason = ""
|
self._abort_reason = ""
|
||||||
@ -241,9 +243,12 @@ class Packer:
|
|||||||
# network shares mapped to Windows drive letters back to their UNC
|
# network shares mapped to Windows drive letters back to their UNC
|
||||||
# notation. Only resolving one but not the other (which can happen
|
# notation. Only resolving one but not the other (which can happen
|
||||||
# with the abosolute() call above) can cause errors.
|
# with the abosolute() call above) can cause errors.
|
||||||
bfile_pp = self._target_path / bfile_path.relative_to(
|
if self.keep_hierarchy:
|
||||||
bpathlib.make_absolute(self.project)
|
bfile_pp = self._target_path / bpathlib.strip_root(bfile_path)
|
||||||
)
|
else:
|
||||||
|
bfile_pp = self._target_path / bfile_path.relative_to(
|
||||||
|
bpathlib.make_absolute(self.project)
|
||||||
|
)
|
||||||
self._output_path = bfile_pp
|
self._output_path = bfile_pp
|
||||||
|
|
||||||
self._progress_cb.pack_start()
|
self._progress_cb.pack_start()
|
||||||
@ -335,7 +340,10 @@ class Packer:
|
|||||||
self._new_location_paths.add(asset_path)
|
self._new_location_paths.add(asset_path)
|
||||||
else:
|
else:
|
||||||
log.debug("%s can keep using %s", bfile_path, usage.asset_path)
|
log.debug("%s can keep using %s", bfile_path, usage.asset_path)
|
||||||
asset_pp = self._target_path / asset_path.relative_to(self.project)
|
if self.keep_hierarchy:
|
||||||
|
asset_pp = self._target_path / bpathlib.strip_root(asset_path)
|
||||||
|
else:
|
||||||
|
asset_pp = self._target_path / asset_path.relative_to(self.project)
|
||||||
act.new_path = asset_pp
|
act.new_path = asset_pp
|
||||||
|
|
||||||
def _find_new_paths(self):
|
def _find_new_paths(self):
|
||||||
@ -346,7 +354,10 @@ class Packer:
|
|||||||
assert isinstance(act, AssetAction)
|
assert isinstance(act, AssetAction)
|
||||||
|
|
||||||
relpath = bpathlib.strip_root(path)
|
relpath = bpathlib.strip_root(path)
|
||||||
act.new_path = pathlib.Path(self._target_path, "_outside_project", relpath)
|
if self.keep_hierarchy:
|
||||||
|
act.new_path = pathlib.Path(self._target_path, relpath)
|
||||||
|
else:
|
||||||
|
act.new_path = pathlib.Path(self._target_path, "_outside_project", relpath)
|
||||||
|
|
||||||
def _group_rewrites(self) -> None:
|
def _group_rewrites(self) -> None:
|
||||||
"""For each blend file, collect which fields need rewriting.
|
"""For each blend file, collect which fields need rewriting.
|
||||||
|
|||||||
27
blender_asset_tracer/preferences.py
Normal file
27
blender_asset_tracer/preferences.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import AddonPreferences
|
||||||
|
from bpy.props import BoolProperty, StringProperty
|
||||||
|
|
||||||
|
|
||||||
|
class BATPreferences(AddonPreferences):
|
||||||
|
bl_idname = "blender_asset_tracer"
|
||||||
|
|
||||||
|
use_env_root: BoolProperty(
|
||||||
|
name="Use Environment Variable for Root",
|
||||||
|
description="Read the project root from ZIP_ROOT or PROJECT_ROOT environment variables",
|
||||||
|
default=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
root_default: StringProperty(
|
||||||
|
name="Default Root",
|
||||||
|
description="Fallback project root when the environment variable is not set",
|
||||||
|
default="",
|
||||||
|
subtype="DIR_PATH",
|
||||||
|
)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, "use_env_root")
|
||||||
|
layout.prop(self, "root_default")
|
||||||
|
|
||||||
|
|
||||||
0
blender_asset_tracer/py.typed
Normal file
0
blender_asset_tracer/py.typed
Normal file
@ -119,6 +119,7 @@ def image(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]
|
|||||||
|
|
||||||
|
|
||||||
@dna_code("LI")
|
@dna_code("LI")
|
||||||
|
@skip_packed
|
||||||
def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
"""Library data blocks."""
|
"""Library data blocks."""
|
||||||
path, field = block.get(b"name", return_field=True)
|
path, field = block.get(b"name", return_field=True)
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import logging
|
|||||||
import typing
|
import typing
|
||||||
|
|
||||||
from blender_asset_tracer import blendfile, bpathlib, cdefs
|
from blender_asset_tracer import blendfile, bpathlib, cdefs
|
||||||
|
from blender_asset_tracer.blendfile import iterators
|
||||||
from . import result
|
from . import result
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@ -337,7 +338,7 @@ def modifier_dynamic_paint(
|
|||||||
|
|
||||||
surfaces = canvas_settings.get_pointer((b"surfaces", b"first"))
|
surfaces = canvas_settings.get_pointer((b"surfaces", b"first"))
|
||||||
|
|
||||||
for surf_idx, surface in enumerate(blendfile.iterators.listbase(surfaces)):
|
for surf_idx, surface in enumerate(iterators.listbase(surfaces)):
|
||||||
surface_block_name = block_name + b".canvas_settings.surfaces[%d]" % (surf_idx)
|
surface_block_name = block_name + b".canvas_settings.surfaces[%d]" % (surf_idx)
|
||||||
point_cache = surface.get_pointer(b"pointcache")
|
point_cache = surface.get_pointer(b"pointcache")
|
||||||
if point_cache is None:
|
if point_cache is None:
|
||||||
@ -351,3 +352,62 @@ 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]:
|
||||||
|
if not modifier.has_field(b"simulation_bake_directory"):
|
||||||
|
return
|
||||||
|
|
||||||
|
mod_directory_ptr, mod_directory_field = modifier.get(
|
||||||
|
b"simulation_bake_directory", return_field=True
|
||||||
|
)
|
||||||
|
|
||||||
|
bakes = modifier.get_pointer(b"bakes")
|
||||||
|
if not bakes:
|
||||||
|
return
|
||||||
|
|
||||||
|
mod_bake_target = modifier.get(b"bake_target")
|
||||||
|
|
||||||
|
for bake_idx, bake in enumerate(iterators.dynamic_array(bakes)):
|
||||||
|
# Check for packed data.
|
||||||
|
bake_target = bake.get(b"bake_target")
|
||||||
|
if bake_target == cdefs.NODES_MODIFIER_BAKE_TARGET_INHERIT:
|
||||||
|
bake_target = mod_bake_target
|
||||||
|
if bake_target == cdefs.NODES_MODIFIER_BAKE_TARGET_PACKED:
|
||||||
|
# This data is packed in the blend file, it's not a dependency to trace.
|
||||||
|
continue
|
||||||
|
|
||||||
|
flag = bake.get(b"flag")
|
||||||
|
use_custom_directory = bool(flag & cdefs.NODES_MODIFIER_BAKE_CUSTOM_PATH)
|
||||||
|
|
||||||
|
if use_custom_directory:
|
||||||
|
bake_directory_ptr, bake_directory_field = bake.get(
|
||||||
|
b"directory", return_field=True
|
||||||
|
)
|
||||||
|
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 = bpathlib.BlendPath(directory.as_bytes_string())
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|||||||
@ -24,9 +24,9 @@ copyright = '2018, Sybren A. Stüvel'
|
|||||||
author = 'Sybren A. Stüvel'
|
author = 'Sybren A. Stüvel'
|
||||||
|
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
version = '1.19'
|
version = '1.21'
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
release = '1.19'
|
release = '1.21'
|
||||||
|
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|||||||
1845
poetry.lock
generated
1845
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "blender-asset-tracer"
|
name = "blender-asset-tracer"
|
||||||
version = "1.19"
|
version = "1.21"
|
||||||
homepage = 'https://developer.blender.org/project/profile/79/'
|
homepage = 'https://developer.blender.org/project/profile/79/'
|
||||||
|
|
||||||
description = "BAT parses Blend files and produces dependency information. After installation run `bat --help`"
|
description = "BAT parses Blend files and produces dependency information. After installation run `bat --help`"
|
||||||
@ -24,19 +24,19 @@ s3 = ["boto3"]
|
|||||||
zstandard = ["zstandard"]
|
zstandard = ["zstandard"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = ">=3.11,<4.0"
|
||||||
requests = "^2.11"
|
requests = "^2.11"
|
||||||
|
|
||||||
# For S3 storage support:
|
# For S3 storage support:
|
||||||
boto3 = { version = "^1.9", optional = true }
|
boto3 = { version = "^1.9", optional = true }
|
||||||
|
|
||||||
# For Blender 3.0+ compressed file support.
|
# For Blender 3.0+ compressed file support.
|
||||||
zstandard = { version = "^0.15", optional = true }
|
zstandard = { version = "^0.16", optional = true }
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
mypy = ">=0.942"
|
mypy = ">=0.942"
|
||||||
pytest = "^6.2"
|
pytest = "^7.4.4"
|
||||||
pytest-cov = "^3.0.0"
|
pytest-cov = "^4.1.0"
|
||||||
twine = "^5.0.0"
|
twine = "^5.0.0"
|
||||||
# for the 'radon cc' command
|
# for the 'radon cc' command
|
||||||
radon = "^3.0"
|
radon = "^3.0"
|
||||||
@ -55,5 +55,5 @@ bat = 'blender_asset_tracer.cli:cli_main'
|
|||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry>=0.12"]
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
build-backend = "poetry.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
addopts = -v --cov blender_asset_tracer --cov-report term-missing
|
addopts = -p pytest_cov -v --cov blender_asset_tracer --cov-report term-missing
|
||||||
|
|
||||||
[pep8]
|
[pep8]
|
||||||
max-line-length = 100
|
max-line-length = 100
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
# This should match pyproject.toml
|
# This should match pyproject.toml
|
||||||
python_version = 3.9
|
python_version = 3.11
|
||||||
|
|
||||||
warn_redundant_casts = True
|
warn_redundant_casts = True
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|||||||
BIN
tests/blendfiles/74871-packed-libraries.blend
Normal file
BIN
tests/blendfiles/74871-packed-libraries.blend
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
BIN
tests/blendfiles/geometry-nodes-sim/geonodes-sim-cache.blend
Normal file
BIN
tests/blendfiles/geometry-nodes-sim/geonodes-sim-cache.blend
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00001_00000.blob","start":39364,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00002_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00003_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"items":{"0":{"name":"Geometry","type":"GEOMETRY","data":{"mesh":{"num_vertices":2012,"num_edges":3978,"num_polygons":1968,"num_corners":7872,"poly_offsets":{"name":"00001_00000.blob","start":0,"size":7876},"materials":[],"attributes":[{"name":".corner_edge","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":7876,"size":31488}},{"name":"position","domain":"POINT","type":"FLOAT_VECTOR","data":{"name":"00004_00000.blob","start":0,"size":24144}},{"name":".edge_verts","domain":"EDGE","type":"INT32_2D","data":{"name":"00001_00000.blob","start":63508,"size":31824}},{"name":".corner_vert","domain":"CORNER","type":"INT","data":{"name":"00001_00000.blob","start":95332,"size":31488}},{"name":"UVMap","domain":"CORNER","type":"FLOAT2","data":{"name":"00001_00000.blob","start":126820,"size":62976}},{"name":".uv_select_vert","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".uv_select_edge","domain":"CORNER","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":189796,"size":7872}},{"name":".select_vert","domain":"POINT","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":197668,"size":2012}},{"name":".select_edge","domain":"EDGE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":199680,"size":3978}},{"name":".select_poly","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":203658,"size":1968}},{"name":".uv_select_face","domain":"FACE","type":"BOOLEAN","data":{"name":"00001_00000.blob","start":205626,"size":1968}}]}}}}}
|
||||||
BIN
tests/blendfiles/multiple_geometry_nodes_bakes.blend
Normal file
BIN
tests/blendfiles/multiple_geometry_nodes_bakes.blend
Normal file
Binary file not shown.
@ -93,6 +93,10 @@ class StructTest(unittest.TestCase):
|
|||||||
self.s_ulong = dna.Struct(b"ulong", 8)
|
self.s_ulong = dna.Struct(b"ulong", 8)
|
||||||
self.s_uint64 = dna.Struct(b"uint64_t", 8)
|
self.s_uint64 = dna.Struct(b"uint64_t", 8)
|
||||||
self.s_uint128 = dna.Struct(b"uint128_t", 16) # non-supported type
|
self.s_uint128 = dna.Struct(b"uint128_t", 16) # non-supported type
|
||||||
|
self.s_substruct = dna.Struct(b"substruct")
|
||||||
|
|
||||||
|
self.f_substruct_name = dna.Field(self.s_char, dna.Name(b"name[10]"), 10, 0)
|
||||||
|
self.s_substruct.append_field(self.f_substruct_name)
|
||||||
|
|
||||||
self.f_next = dna.Field(self.s, dna.Name(b"*next"), 8, 0)
|
self.f_next = dna.Field(self.s, dna.Name(b"*next"), 8, 0)
|
||||||
self.f_prev = dna.Field(self.s, dna.Name(b"*prev"), 8, 8)
|
self.f_prev = dna.Field(self.s, dna.Name(b"*prev"), 8, 8)
|
||||||
@ -109,6 +113,7 @@ class StructTest(unittest.TestCase):
|
|||||||
self.f_testint = dna.Field(self.s_int, dna.Name(b"testint"), 4, 4178)
|
self.f_testint = dna.Field(self.s_int, dna.Name(b"testint"), 4, 4178)
|
||||||
self.f_testfloat = dna.Field(self.s_float, dna.Name(b"testfloat"), 4, 4182)
|
self.f_testfloat = dna.Field(self.s_float, dna.Name(b"testfloat"), 4, 4182)
|
||||||
self.f_testulong = dna.Field(self.s_ulong, dna.Name(b"testulong"), 8, 4186)
|
self.f_testulong = dna.Field(self.s_ulong, dna.Name(b"testulong"), 8, 4186)
|
||||||
|
self.f_substruct = dna.Field(self.s_substruct, dna.Name(b"testsubstruct"), 10, 4194)
|
||||||
|
|
||||||
self.s.append_field(self.f_next)
|
self.s.append_field(self.f_next)
|
||||||
self.s.append_field(self.f_prev)
|
self.s.append_field(self.f_prev)
|
||||||
@ -125,6 +130,7 @@ class StructTest(unittest.TestCase):
|
|||||||
self.s.append_field(self.f_testint)
|
self.s.append_field(self.f_testint)
|
||||||
self.s.append_field(self.f_testfloat)
|
self.s.append_field(self.f_testfloat)
|
||||||
self.s.append_field(self.f_testulong)
|
self.s.append_field(self.f_testulong)
|
||||||
|
self.s.append_field(self.f_substruct)
|
||||||
|
|
||||||
def test_autosize(self):
|
def test_autosize(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
@ -263,6 +269,19 @@ class StructTest(unittest.TestCase):
|
|||||||
self.assertAlmostEqual(2.79, val[1])
|
self.assertAlmostEqual(2.79, val[1])
|
||||||
fileobj.seek.assert_called_with(4144, os.SEEK_CUR)
|
fileobj.seek.assert_called_with(4144, os.SEEK_CUR)
|
||||||
|
|
||||||
|
def test_struct_field_get_subproperty(self):
|
||||||
|
fileobj = mock.MagicMock(io.BufferedReader)
|
||||||
|
fileobj.read.return_value = b"my_name"
|
||||||
|
|
||||||
|
_, val = self.s.field_get(
|
||||||
|
self.FakeHeader(),
|
||||||
|
fileobj,
|
||||||
|
(b"testsubstruct", b"name"),
|
||||||
|
as_str=True,
|
||||||
|
)
|
||||||
|
self.assertEqual("my_name", val)
|
||||||
|
fileobj.seek.assert_called_with(4194, os.SEEK_CUR)
|
||||||
|
|
||||||
def test_char_field_set(self):
|
def test_char_field_set(self):
|
||||||
fileobj = mock.MagicMock(io.BufferedReader)
|
fileobj = mock.MagicMock(io.BufferedReader)
|
||||||
value = 255
|
value = 255
|
||||||
@ -344,3 +363,10 @@ class StructTest(unittest.TestCase):
|
|||||||
expected = struct.pack(b">f", value)
|
expected = struct.pack(b">f", value)
|
||||||
self.s.field_set(self.FakeHeader(), fileobj, b"testfloat", value)
|
self.s.field_set(self.FakeHeader(), fileobj, b"testfloat", value)
|
||||||
fileobj.write.assert_called_with(expected)
|
fileobj.write.assert_called_with(expected)
|
||||||
|
|
||||||
|
def test_struct_field_set_subproperty(self):
|
||||||
|
fileobj = mock.MagicMock(io.BufferedReader)
|
||||||
|
expected = b"new_name\x00"
|
||||||
|
|
||||||
|
self.s.field_set(self.FakeHeader(), fileobj, (b"testsubstruct", b"name"), "new_name")
|
||||||
|
fileobj.write.assert_called_with(expected)
|
||||||
|
|||||||
@ -46,6 +46,12 @@ class BlendFileBlockTest(AbstractBlendFileTest):
|
|||||||
mname = mesh.get((b"id", b"name"), as_str=True)
|
mname = mesh.get((b"id", b"name"), as_str=True)
|
||||||
self.assertEqual("MECube³", mname)
|
self.assertEqual("MECube³", mname)
|
||||||
|
|
||||||
|
# Try to access different file-block items.
|
||||||
|
verts_ptr = mesh.get(b"mvert")
|
||||||
|
verts = self.bf.block_from_addr[verts_ptr]
|
||||||
|
assert verts.get(b"co") == [-1.0, -1.0, -1.0]
|
||||||
|
assert verts.get(b"co", array_index=1) == [-1.0, -1.0, 1.0]
|
||||||
|
|
||||||
def test_get_recursive_iter(self):
|
def test_get_recursive_iter(self):
|
||||||
ob = self.bf.code_index[b"OB"][0]
|
ob = self.bf.code_index[b"OB"][0]
|
||||||
assert isinstance(ob, blendfile.BlendFileBlock)
|
assert isinstance(ob, blendfile.BlendFileBlock)
|
||||||
@ -297,6 +303,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
|
||||||
|
|||||||
146
tests/test_pack.py
Normal file → Executable file
146
tests/test_pack.py
Normal file → Executable file
@ -694,6 +694,152 @@ class ProgressTest(AbstractPackTest):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KeepHierarchyPackTest(AbstractPackTest):
|
||||||
|
def hierarchy_path(self, filepath) -> Path:
|
||||||
|
"""Return the keep-hierarchy path for a file: target / strip_root(abs_path)."""
|
||||||
|
return Path(self.tpath, bpathlib.strip_root(filepath))
|
||||||
|
|
||||||
|
def test_strategise_keep_hierarchy_no_rewrite(self):
|
||||||
|
"""When all deps are in-project with relative paths, no rewriting is needed."""
|
||||||
|
infile = self.blendfiles / "doubly_linked.blend"
|
||||||
|
|
||||||
|
packer = pack.Packer(
|
||||||
|
infile, self.blendfiles, self.tpath, keep_hierarchy=True
|
||||||
|
)
|
||||||
|
packer.strategise()
|
||||||
|
|
||||||
|
packed_files = (
|
||||||
|
"doubly_linked.blend",
|
||||||
|
"linked_cube.blend",
|
||||||
|
"basic_file.blend",
|
||||||
|
"material_textures.blend",
|
||||||
|
"textures/Bricks/brick_dotted_04-bump.jpg",
|
||||||
|
"textures/Bricks/brick_dotted_04-color.jpg",
|
||||||
|
)
|
||||||
|
for pf in packed_files:
|
||||||
|
path = self.blendfiles / pf
|
||||||
|
act = packer._actions[path]
|
||||||
|
self.assertEqual(
|
||||||
|
pack.PathAction.KEEP_PATH, act.path_action, "for %s" % pf
|
||||||
|
)
|
||||||
|
# In keep_hierarchy mode, paths use strip_root(abs_path) instead of
|
||||||
|
# relative_to(project).
|
||||||
|
self.assertEqual(
|
||||||
|
self.hierarchy_path(path), act.new_path, "for %s" % pf
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual({}, self.rewrites(packer))
|
||||||
|
self.assertEqual(len(packed_files), len(packer._actions))
|
||||||
|
|
||||||
|
def test_strategise_keep_hierarchy_rewrite(self):
|
||||||
|
"""Deps outside the project go to target/strip_root(path), not _outside_project/."""
|
||||||
|
ppath = self.blendfiles / "subdir"
|
||||||
|
infile = ppath / "doubly_linked_up.blend"
|
||||||
|
|
||||||
|
packer = pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True)
|
||||||
|
packer.strategise()
|
||||||
|
|
||||||
|
# The blendfile itself should be at target / strip_root(abs_path)
|
||||||
|
act = packer._actions[infile]
|
||||||
|
self.assertEqual(pack.PathAction.KEEP_PATH, act.path_action)
|
||||||
|
self.assertEqual(self.hierarchy_path(infile), act.new_path)
|
||||||
|
|
||||||
|
# External files should NOT be under _outside_project/
|
||||||
|
external_files = (
|
||||||
|
"linked_cube.blend",
|
||||||
|
"basic_file.blend",
|
||||||
|
"material_textures.blend",
|
||||||
|
"textures/Bricks/brick_dotted_04-bump.jpg",
|
||||||
|
"textures/Bricks/brick_dotted_04-color.jpg",
|
||||||
|
)
|
||||||
|
for fn in external_files:
|
||||||
|
path = self.blendfiles / fn
|
||||||
|
act = packer._actions[path]
|
||||||
|
self.assertEqual(
|
||||||
|
pack.PathAction.FIND_NEW_LOCATION, act.path_action, "for %s" % fn
|
||||||
|
)
|
||||||
|
# Should be at target / strip_root(abs_path), NOT target/_outside_project/...
|
||||||
|
expected = self.hierarchy_path(path)
|
||||||
|
self.assertEqual(
|
||||||
|
expected,
|
||||||
|
act.new_path,
|
||||||
|
f"\nEXPECT: {expected}\nACTUAL: {act.new_path}\nfor {fn}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# There should be no _outside_project in any new_path
|
||||||
|
for path, action in packer._actions.items():
|
||||||
|
self.assertNotIn(
|
||||||
|
"_outside_project",
|
||||||
|
str(action.new_path),
|
||||||
|
f"_outside_project should not appear in keep_hierarchy mode for {path}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_execute_keep_hierarchy(self):
|
||||||
|
"""Verify files are copied to correct hierarchy and paths are rewritten."""
|
||||||
|
ppath = self.blendfiles / "subdir"
|
||||||
|
infile = ppath / "doubly_linked_up.blend"
|
||||||
|
|
||||||
|
with pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
|
||||||
|
# The blendfile should be at its hierarchy position
|
||||||
|
packed_blend = self.hierarchy_path(infile)
|
||||||
|
self.assertTrue(packed_blend.exists(), "Blendfile should be in hierarchy")
|
||||||
|
|
||||||
|
# There should be NO _outside_project directory
|
||||||
|
self.assertFalse(
|
||||||
|
(self.tpath / "_outside_project").exists(),
|
||||||
|
"_outside_project should not exist in keep_hierarchy mode",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Dependencies should be at their hierarchy positions
|
||||||
|
for fn in ("linked_cube.blend", "basic_file.blend", "material_textures.blend"):
|
||||||
|
dep_path = self.hierarchy_path(self.blendfiles / fn)
|
||||||
|
self.assertTrue(dep_path.exists(), "%s should be in hierarchy" % fn)
|
||||||
|
|
||||||
|
# Verify paths were rewritten correctly in the packed blend file.
|
||||||
|
# The rewritten paths should be relative from the packed blend to
|
||||||
|
# the packed dependencies.
|
||||||
|
bfile = blendfile.open_cached(packed_blend, assert_cached=False)
|
||||||
|
libs = sorted(bfile.code_index[b"LI"])
|
||||||
|
|
||||||
|
# Since keep_hierarchy preserves relative positions, the relative paths
|
||||||
|
# from subdir/doubly_linked_up.blend to the parent blendfiles should
|
||||||
|
# be the same as the originals (//../linked_cube.blend etc.)
|
||||||
|
self.assertEqual(b"LILib", libs[0].id_name)
|
||||||
|
self.assertEqual(b"//../linked_cube.blend", libs[0][b"name"])
|
||||||
|
self.assertEqual(b"LILib.002", libs[1].id_name)
|
||||||
|
self.assertEqual(b"//../material_textures.blend", libs[1][b"name"])
|
||||||
|
|
||||||
|
def test_execute_keep_hierarchy_no_touch_origs(self):
|
||||||
|
"""Original files should not be modified."""
|
||||||
|
ppath = self.blendfiles / "subdir"
|
||||||
|
infile = ppath / "doubly_linked_up.blend"
|
||||||
|
|
||||||
|
with pack.Packer(infile, ppath, self.tpath, keep_hierarchy=True) as packer:
|
||||||
|
packer.strategise()
|
||||||
|
packer.execute()
|
||||||
|
|
||||||
|
# The original file shouldn't be touched.
|
||||||
|
bfile = blendfile.open_cached(infile, assert_cached=False)
|
||||||
|
libs = sorted(bfile.code_index[b"LI"])
|
||||||
|
self.assertEqual(b"LILib", libs[0].id_name)
|
||||||
|
self.assertEqual(b"//../linked_cube.blend", libs[0][b"name"])
|
||||||
|
self.assertEqual(b"LILib.002", libs[1].id_name)
|
||||||
|
self.assertEqual(b"//../material_textures.blend", libs[1][b"name"])
|
||||||
|
|
||||||
|
def test_keep_hierarchy_output_path(self):
|
||||||
|
"""output_path should use the full hierarchy path."""
|
||||||
|
infile = self.blendfiles / "basic_file.blend"
|
||||||
|
packer = pack.Packer(
|
||||||
|
infile, self.blendfiles, self.tpath, keep_hierarchy=True
|
||||||
|
)
|
||||||
|
packer.strategise()
|
||||||
|
|
||||||
|
self.assertEqual(self.hierarchy_path(infile), packer.output_path)
|
||||||
|
|
||||||
|
|
||||||
class AbortTest(AbstractPackTest):
|
class AbortTest(AbstractPackTest):
|
||||||
def test_abort_strategise(self):
|
def test_abort_strategise(self):
|
||||||
infile = self.blendfiles / "subdir/doubly_linked_up.blend"
|
infile = self.blendfiles / "subdir/doubly_linked_up.blend"
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import collections
|
import collections
|
||||||
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import typing
|
import typing
|
||||||
@ -369,6 +370,10 @@ class DepsTest(AbstractTracerTest):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_block_li_packed(self):
|
||||||
|
# Packed libraries should not be traced.
|
||||||
|
self.assert_deps("74871-packed-libraries.blend", {})
|
||||||
|
|
||||||
def test_deps_recursive(self):
|
def test_deps_recursive(self):
|
||||||
self.assert_deps(
|
self.assert_deps(
|
||||||
"doubly_linked.blend",
|
"doubly_linked.blend",
|
||||||
@ -552,6 +557,56 @@ class DepsTest(AbstractTracerTest):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_geonodes_sim_data(self) -> None:
|
||||||
|
# Simplify the rest of the code by putting the values that are the same of all cases here:
|
||||||
|
expect_bake = functools.partial(
|
||||||
|
Expect,
|
||||||
|
dirname_field=None,
|
||||||
|
basename_field=None,
|
||||||
|
is_sequence=True,
|
||||||
|
)
|
||||||
|
expects = {
|
||||||
|
# Two objects that use "Inherit from Modifer":
|
||||||
|
b"OBCustom Bake Path.modifiers[0].bakes[0]": [
|
||||||
|
# Custom path set on the sim node, so this is sim node data.
|
||||||
|
expect_bake(
|
||||||
|
type="NodesModifierBake",
|
||||||
|
full_field="*directory",
|
||||||
|
asset_path=b"//bakePath",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
b"OBDefault Bake Path.modifiers[0].bakes[0]": [
|
||||||
|
# NO custom path set on the sim node, so this follows the modifier data.
|
||||||
|
expect_bake(
|
||||||
|
type="NodesModifierData",
|
||||||
|
full_field="*simulation_bake_directory",
|
||||||
|
asset_path=b"//config-on-sim-node",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
# Two objects that have the config only on the node itself:
|
||||||
|
b"OBCustom Bake Path.001.modifiers[0].bakes[0]": [
|
||||||
|
expect_bake(
|
||||||
|
type="NodesModifierBake",
|
||||||
|
full_field="*directory",
|
||||||
|
asset_path=b"//set-on-node",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
b"OBDefault Bake Path.001.modifiers[0].bakes[0]": [
|
||||||
|
expect_bake(
|
||||||
|
type="NodesModifierData",
|
||||||
|
full_field="*simulation_bake_directory",
|
||||||
|
asset_path=b"//only-set-on-modifier",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# NOTE: there are two more objects in the scene, 'Packed Bake' and
|
||||||
|
# 'Packed Bake.001'. But, because those use packed data (on the modifier
|
||||||
|
# resp. bake level), they should not be listed as dependencies.
|
||||||
|
|
||||||
|
self.maxDiff = None
|
||||||
|
self.assert_deps("geometry-nodes-sim/geonodes-sim-cache.blend", expects)
|
||||||
|
|
||||||
def test_recursion_loop(self):
|
def test_recursion_loop(self):
|
||||||
infinite_bfile = self.blendfiles / "recursive_dependency_1.blend"
|
infinite_bfile = self.blendfiles / "recursive_dependency_1.blend"
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user