Compare commits
25 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 | ||
|
|
08b37a35f8 | ||
|
|
6c42d06f05 | ||
|
|
16c208bc8e | ||
|
|
4c429e9212 |
18
CHANGELOG.md
18
CHANGELOG.md
@ -3,6 +3,24 @@
|
|||||||
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.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)).
|
||||||
|
- Make it possible to run BAT with `python -m blender_asset_tracer` ([6c42d06f0590](https://projects.blender.org/blender/blender-asset-tracer/commit/6c42d06f05909d4ac2096e84557d19dd93382f3a)).
|
||||||
|
- Add support for loading the file sub-version ([4c429e921228](https://projects.blender.org/blender/blender-asset-tracer/commit/4c429e921228259f47795f8ad913ad3eff8fac71)).
|
||||||
|
|
||||||
|
|
||||||
# Version 1.19 (2025-06-16)
|
# Version 1.19 (2025-06-16)
|
||||||
|
|
||||||
- Add support for tracing dynamic paint caches ([#92889](https://projects.blender.org/blender/blender-asset-tracer/pulls/92889)).
|
- Add support for tracing dynamic paint caches ([#92889](https://projects.blender.org/blender/blender-asset-tracer/pulls/92889)).
|
||||||
|
|||||||
@ -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,6 +75,7 @@ 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:
|
||||||
|
|
||||||
|
```python
|
||||||
#!/usr/bin/env python3.7
|
#!/usr/bin/env python3.7
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
@ -118,6 +119,7 @@ open Blender itself. Here is an example showing how to determine the render engi
|
|||||||
|
|
||||||
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()
|
||||||
|
|||||||
3
blender_asset_tracer/__main__.py
Normal file
3
blender_asset_tracer/__main__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from blender_asset_tracer import cli
|
||||||
|
|
||||||
|
cli.cli_main()
|
||||||
@ -122,6 +122,7 @@ class BlendFile:
|
|||||||
self.filepath = path
|
self.filepath = path
|
||||||
self.raw_filepath = path
|
self.raw_filepath = path
|
||||||
self._is_modified = False
|
self._is_modified = False
|
||||||
|
self.file_subversion = 0
|
||||||
self.fileobj = self._open_file(path, mode)
|
self.fileobj = self._open_file(path, mode)
|
||||||
|
|
||||||
self.blocks = [] # type: BFBList
|
self.blocks = [] # type: BFBList
|
||||||
@ -169,6 +170,8 @@ class BlendFile:
|
|||||||
|
|
||||||
if block.code == b"DNA1":
|
if block.code == b"DNA1":
|
||||||
self.decode_structs(block)
|
self.decode_structs(block)
|
||||||
|
elif block.code == b"GLOB":
|
||||||
|
self.decode_glob(block)
|
||||||
else:
|
else:
|
||||||
self.fileobj.seek(block.size, os.SEEK_CUR)
|
self.fileobj.seek(block.size, os.SEEK_CUR)
|
||||||
|
|
||||||
@ -356,6 +359,25 @@ class BlendFile:
|
|||||||
dna_struct.append_field(field)
|
dna_struct.append_field(field)
|
||||||
dna_offset += dna_size
|
dna_offset += dna_size
|
||||||
|
|
||||||
|
def decode_glob(self, block: "BlendFileBlock") -> None:
|
||||||
|
"""Partially decode the GLOB block to get the file sub-version."""
|
||||||
|
# Before this, the subversion didn't exist in 'FileGlobal'.
|
||||||
|
if self.header.version <= 242:
|
||||||
|
self.file_subversion = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
# GLOB can appear in the file before DNA1, and so we cannot use DNA to
|
||||||
|
# parse the fields.
|
||||||
|
|
||||||
|
# The subversion is always the `short` at offset 4.
|
||||||
|
# block_data = io.BytesIO(block.raw_data())
|
||||||
|
endian = self.header.endian
|
||||||
|
self.fileobj.seek(4, os.SEEK_CUR) # Skip the next 4 bytes.
|
||||||
|
self.file_subversion = endian.read_short(self.fileobj)
|
||||||
|
|
||||||
|
# Skip to the next block.
|
||||||
|
self.fileobj.seek(block.file_offset + block.size, os.SEEK_SET)
|
||||||
|
|
||||||
def abspath(self, relpath: bpathlib.BlendPath) -> bpathlib.BlendPath:
|
def abspath(self, relpath: bpathlib.BlendPath) -> bpathlib.BlendPath:
|
||||||
"""Construct an absolute path from a blendfile-relative path."""
|
"""Construct an absolute path from a blendfile-relative path."""
|
||||||
|
|
||||||
@ -425,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
|
||||||
|
|
||||||
@ -568,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.
|
||||||
|
|
||||||
@ -584,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(
|
||||||
@ -605,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)
|
||||||
@ -614,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(
|
||||||
@ -680,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)
|
||||||
@ -789,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 ""
|
||||||
|
|
||||||
|
|
||||||
11
blender_asset_tracer/pack/__init__.py
Normal file → Executable file
11
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,6 +243,9 @@ 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.
|
||||||
|
if self.keep_hierarchy:
|
||||||
|
bfile_pp = self._target_path / bpathlib.strip_root(bfile_path)
|
||||||
|
else:
|
||||||
bfile_pp = self._target_path / bfile_path.relative_to(
|
bfile_pp = self._target_path / bfile_path.relative_to(
|
||||||
bpathlib.make_absolute(self.project)
|
bpathlib.make_absolute(self.project)
|
||||||
)
|
)
|
||||||
@ -335,6 +340,9 @@ 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)
|
||||||
|
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)
|
asset_pp = self._target_path / asset_path.relative_to(self.project)
|
||||||
act.new_path = asset_pp
|
act.new_path = asset_pp
|
||||||
|
|
||||||
@ -346,6 +354,9 @@ class Packer:
|
|||||||
assert isinstance(act, AssetAction)
|
assert isinstance(act, AssetAction)
|
||||||
|
|
||||||
relpath = bpathlib.strip_root(path)
|
relpath = bpathlib.strip_root(path)
|
||||||
|
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)
|
act.new_path = pathlib.Path(self._target_path, "_outside_project", relpath)
|
||||||
|
|
||||||
def _group_rewrites(self) -> None:
|
def _group_rewrites(self) -> None:
|
||||||
|
|||||||
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)
|
||||||
|
|||||||
@ -90,7 +90,14 @@ def _expand_generic_mtex(block: blendfile.BlendFileBlock):
|
|||||||
|
|
||||||
|
|
||||||
def _expand_generic_nodetree(block: blendfile.BlendFileBlock):
|
def _expand_generic_nodetree(block: blendfile.BlendFileBlock):
|
||||||
assert block.dna_type.dna_type_id == b"bNodeTree"
|
if block.dna_type.dna_type_id == b"ID":
|
||||||
|
# This is a placeholder for a linked node tree.
|
||||||
|
yield block
|
||||||
|
return
|
||||||
|
|
||||||
|
assert (
|
||||||
|
block.dna_type.dna_type_id == b"bNodeTree"
|
||||||
|
), f"Expected bNodeTree, got {block.dna_type.dna_type_id.decode()})"
|
||||||
|
|
||||||
nodes = block.get_pointer((b"nodes", b"first"))
|
nodes = block.get_pointer((b"nodes", b"first"))
|
||||||
|
|
||||||
@ -145,6 +152,10 @@ def _expand_generic_idprops(block: blendfile.BlendFileBlock):
|
|||||||
|
|
||||||
|
|
||||||
def _expand_generic_nodetree_id(block: blendfile.BlendFileBlock):
|
def _expand_generic_nodetree_id(block: blendfile.BlendFileBlock):
|
||||||
|
if block.bfile.header.version >= 500 and block.bfile.file_subversion >= 4:
|
||||||
|
# Introduced in Blender 5.0, commit bd61e69be5a7c96f1e5da1c86aafc17b839e049f
|
||||||
|
block_ntree = block.get_pointer(b"compositing_node_group", None)
|
||||||
|
else:
|
||||||
block_ntree = block.get_pointer(b"nodetree", None)
|
block_ntree = block.get_pointer(b"nodetree", None)
|
||||||
if block_ntree is not None:
|
if block_ntree is not None:
|
||||||
yield from _expand_generic_nodetree(block_ntree)
|
yield from _expand_generic_nodetree(block_ntree)
|
||||||
|
|||||||
@ -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.
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
|
||||||
@ -477,3 +506,15 @@ class BlendFileCacheTest(AbstractBlendFileTest):
|
|||||||
self.assertIs(bf, blendfile._cached_bfiles[other])
|
self.assertIs(bf, blendfile._cached_bfiles[other])
|
||||||
|
|
||||||
self.assertEqual(str(bf.raw_filepath), bf.fileobj.name)
|
self.assertEqual(str(bf.raw_filepath), bf.fileobj.name)
|
||||||
|
|
||||||
|
|
||||||
|
class BlendFileSubVersionTest(AbstractBlendFileTest):
|
||||||
|
def test_file_subversion(self) -> None:
|
||||||
|
self.bf = blendfile.BlendFile(self.blendfiles / "multiple_materials.blend")
|
||||||
|
self.assertEqual(self.bf.file_subversion, 3)
|
||||||
|
|
||||||
|
self.bf = blendfile.BlendFile(
|
||||||
|
self.blendfiles
|
||||||
|
/ "compositor_nodes/compositor_nodes_blender500_library.blend"
|
||||||
|
)
|
||||||
|
self.assertEqual(self.bf.file_subversion, 36)
|
||||||
|
|||||||
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",
|
||||||
@ -506,6 +511,26 @@ class DepsTest(AbstractTracerTest):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_compositor_nodes(self) -> None:
|
||||||
|
"""Test compositor node trees.
|
||||||
|
|
||||||
|
Since Blender 5.0 these use a different DNA field, and can also be
|
||||||
|
linked from other files.
|
||||||
|
"""
|
||||||
|
self.assert_deps(
|
||||||
|
"compositor_nodes/compositor_nodes_blender500_workfile.blend",
|
||||||
|
{
|
||||||
|
b"LIcompositor_nodes_blender500_library.blend": Expect(
|
||||||
|
type="Library",
|
||||||
|
full_field="name[1024]",
|
||||||
|
dirname_field=None,
|
||||||
|
basename_field=None,
|
||||||
|
asset_path=b"//compositor_nodes_blender500_library.blend",
|
||||||
|
is_sequence=False,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
def test_usage_abspath(self):
|
def test_usage_abspath(self):
|
||||||
deps = [
|
deps = [
|
||||||
dep
|
dep
|
||||||
@ -532,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