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,
|
||||
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)).
|
||||
- Make it possible to run BAT with `python -m blender_asset_tracer` ([6c42d06f0590](https://projects.blender.org/blender/blender-asset-tracer/commit/6c42d06f05909d4ac2096e84557d19dd93382f3a)).
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Blender Asset Tracer BAT🦇
|
||||
# Blender Asset Tracer BAT🦇 (ADV fork)
|
||||
|
||||
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
|
||||
open Blender itself. Here is an example showing how to determine the render engine used:
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3.7
|
||||
import json
|
||||
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)
|
||||
print()
|
||||
```
|
||||
|
||||
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
|
||||
@ -171,6 +173,6 @@ index-servers =
|
||||
pip install twine
|
||||
|
||||
poetry build
|
||||
poetry run twine check 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.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.21.tar.gz dist/blender_asset_tracer-1.21-*.whl
|
||||
```
|
||||
|
||||
@ -20,4 +20,57 @@
|
||||
|
||||
# <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 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:
|
||||
self.bfile = bfile
|
||||
|
||||
@ -590,6 +596,7 @@ class BlendFileBlock:
|
||||
null_terminated=True,
|
||||
as_str=False,
|
||||
return_field=False,
|
||||
array_index=0,
|
||||
) -> typing.Any:
|
||||
"""Read a property and return the value.
|
||||
|
||||
@ -606,8 +613,20 @@ class BlendFileBlock:
|
||||
(assumes UTF-8 encoding).
|
||||
:param return_field: When True, returns tuple (dna.Field, 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]
|
||||
field, value = dna_struct.field_get(
|
||||
@ -627,8 +646,8 @@ class BlendFileBlock:
|
||||
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
|
||||
return self.bfile.fileobj.read(self.size)
|
||||
|
||||
def as_string(self) -> str:
|
||||
"""Interpret the bytes of this datablock as null-terminated utf8 string."""
|
||||
def as_bytes_string(self) -> bytes:
|
||||
"""Interpret the bytes of this datablock as null-terminated string of raw bytes."""
|
||||
the_bytes = self.raw_data()
|
||||
try:
|
||||
first_null = the_bytes.index(0)
|
||||
@ -636,6 +655,11 @@ class BlendFileBlock:
|
||||
pass
|
||||
else:
|
||||
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()
|
||||
|
||||
def get_recursive_iter(
|
||||
@ -702,7 +726,7 @@ class BlendFileBlock:
|
||||
hsh = zlib.adler32(str(value).encode(), hsh)
|
||||
return hsh
|
||||
|
||||
def set(self, path: bytes, value):
|
||||
def set(self, path: dna.FieldPath, value):
|
||||
dna_struct = self.bfile.structs[self.sdna_index]
|
||||
self.bfile.mark_modified()
|
||||
self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET)
|
||||
@ -811,9 +835,13 @@ class BlendFileBlock:
|
||||
def __getitem__(self, path: dna.FieldPath):
|
||||
return self.get(path)
|
||||
|
||||
def __setitem__(self, item: bytes, value) -> None:
|
||||
def __setitem__(self, item: dna.FieldPath, value) -> None:
|
||||
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]:
|
||||
"""Generator, yields all field names of this block."""
|
||||
return (f.name.name_only for f in self.dna_type.fields)
|
||||
|
||||
@ -310,7 +310,7 @@ class Struct:
|
||||
self,
|
||||
file_header: header.BlendFileHeader,
|
||||
fileobj: typing.IO[bytes],
|
||||
path: bytes,
|
||||
path: FieldPath,
|
||||
value: typing.Any,
|
||||
):
|
||||
"""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
|
||||
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)
|
||||
|
||||
|
||||
@ -204,17 +204,17 @@ class EndianIO:
|
||||
return fileobj.write(to_write)
|
||||
|
||||
@classmethod
|
||||
def read_bytes0(cls, fileobj, length):
|
||||
def read_bytes0(cls, fileobj: typing.IO[bytes], length: int) -> bytes:
|
||||
data = fileobj.read(length)
|
||||
return cls.read_data0(data)
|
||||
|
||||
@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
|
||||
return data[offset : offset + add]
|
||||
|
||||
@classmethod
|
||||
def read_data0(cls, data):
|
||||
def read_data0(cls, data: bytes) -> bytes:
|
||||
add = data.find(b"\0")
|
||||
if add < 0:
|
||||
return data
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
# (c) 2014, Blender Foundation - Campbell Barton
|
||||
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
||||
import typing
|
||||
import copy
|
||||
|
||||
from blender_asset_tracer import cdefs
|
||||
from . import BlendFileBlock
|
||||
@ -70,3 +71,27 @@ def modifiers(object_block: BlendFileBlock) -> typing.Iterator[BlendFileBlock]:
|
||||
# 'ob->modifiers[...]'
|
||||
mods = object_block.get_pointer((b"modifiers", b"first"))
|
||||
yield from listbase(mods, next_path=(b"modifier", b"next"))
|
||||
|
||||
|
||||
def dynamic_array(block: BlendFileBlock) -> typing.Iterator[BlendFileBlock]:
|
||||
"""
|
||||
Generator that yields each element of a dynamic array as a separate block.
|
||||
|
||||
Dynamic arrays are multiple contiguous elements accessed via a single
|
||||
pointer. BAT interprets these as a single data block, making it hard to
|
||||
access individual elements. This function divides the array into individual
|
||||
blocks by creating modified copies of the original block.
|
||||
|
||||
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_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
|
||||
PART_DRAW_OB = 7
|
||||
PART_DRAW_GR = 8
|
||||
|
||||
@ -112,7 +112,7 @@ def cli_blocks(args):
|
||||
# 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]
|
||||
print(
|
||||
"Biggest %s block is %s at address %s"
|
||||
"Biggest %s block is %s at address 0x%x"
|
||||
% (
|
||||
block_key(biggest_block),
|
||||
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. "
|
||||
"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):
|
||||
@ -119,6 +128,9 @@ def create_packer(
|
||||
if args.relative_only:
|
||||
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))
|
||||
|
||||
elif (
|
||||
@ -137,6 +149,11 @@ def create_packer(
|
||||
"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)
|
||||
|
||||
elif target.lower().endswith(".zip"):
|
||||
@ -146,7 +163,8 @@ def create_packer(
|
||||
raise ValueError("ZIP packer does not support on-the-fly compression")
|
||||
|
||||
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:
|
||||
packer = pack.Packer(
|
||||
@ -156,6 +174,7 @@ def create_packer(
|
||||
noop=args.noop,
|
||||
compress=args.compress,
|
||||
relative_only=args.relative_only,
|
||||
keep_hierarchy=args.keep_hierarchy,
|
||||
)
|
||||
|
||||
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,
|
||||
compress=False,
|
||||
relative_only=False,
|
||||
keep_hierarchy=False,
|
||||
) -> None:
|
||||
self.blendfile = bfile
|
||||
self.project = project
|
||||
@ -111,6 +112,7 @@ class Packer:
|
||||
self.noop = noop
|
||||
self.compress = compress
|
||||
self.relative_only = relative_only
|
||||
self.keep_hierarchy = keep_hierarchy
|
||||
self._aborted = threading.Event()
|
||||
self._abort_lock = threading.RLock()
|
||||
self._abort_reason = ""
|
||||
@ -241,6 +243,9 @@ class Packer:
|
||||
# network shares mapped to Windows drive letters back to their UNC
|
||||
# notation. Only resolving one but not the other (which can happen
|
||||
# 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(
|
||||
bpathlib.make_absolute(self.project)
|
||||
)
|
||||
@ -335,6 +340,9 @@ class Packer:
|
||||
self._new_location_paths.add(asset_path)
|
||||
else:
|
||||
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)
|
||||
act.new_path = asset_pp
|
||||
|
||||
@ -346,6 +354,9 @@ class Packer:
|
||||
assert isinstance(act, AssetAction)
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
@skip_packed
|
||||
def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Library data blocks."""
|
||||
path, field = block.get(b"name", return_field=True)
|
||||
|
||||
@ -27,6 +27,7 @@ import logging
|
||||
import typing
|
||||
|
||||
from blender_asset_tracer import blendfile, bpathlib, cdefs
|
||||
from blender_asset_tracer.blendfile import iterators
|
||||
from . import result
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@ -337,7 +338,7 @@ def modifier_dynamic_paint(
|
||||
|
||||
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)
|
||||
point_cache = surface.get_pointer(b"pointcache")
|
||||
if point_cache is None:
|
||||
@ -351,3 +352,62 @@ def modifier_dynamic_paint(
|
||||
yield from _walk_point_cache(
|
||||
ctx, surface_block_name, modifier.bfile, point_cache, cdefs.PTCACHE_EXT
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_Nodes)
|
||||
def modifier_nodes(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
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'
|
||||
|
||||
# The short X.Y version
|
||||
version = '1.19'
|
||||
version = '1.21'
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '1.19'
|
||||
release = '1.21'
|
||||
|
||||
|
||||
# -- 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]
|
||||
name = "blender-asset-tracer"
|
||||
version = "1.19"
|
||||
version = "1.21"
|
||||
homepage = 'https://developer.blender.org/project/profile/79/'
|
||||
|
||||
description = "BAT parses Blend files and produces dependency information. After installation run `bat --help`"
|
||||
@ -24,19 +24,19 @@ s3 = ["boto3"]
|
||||
zstandard = ["zstandard"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = ">=3.11,<4.0"
|
||||
requests = "^2.11"
|
||||
|
||||
# For S3 storage support:
|
||||
boto3 = { version = "^1.9", optional = true }
|
||||
|
||||
# For Blender 3.0+ compressed file support.
|
||||
zstandard = { version = "^0.15", optional = true }
|
||||
zstandard = { version = "^0.16", optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = ">=0.942"
|
||||
pytest = "^6.2"
|
||||
pytest-cov = "^3.0.0"
|
||||
pytest = "^7.4.4"
|
||||
pytest-cov = "^4.1.0"
|
||||
twine = "^5.0.0"
|
||||
# for the 'radon cc' command
|
||||
radon = "^3.0"
|
||||
@ -55,5 +55,5 @@ bat = 'blender_asset_tracer.cli:cli_main'
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
build-backend = "poetry.masonry.api"
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
[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]
|
||||
max-line-length = 100
|
||||
|
||||
[mypy]
|
||||
# This should match pyproject.toml
|
||||
python_version = 3.9
|
||||
python_version = 3.11
|
||||
|
||||
warn_redundant_casts = 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_uint64 = dna.Struct(b"uint64_t", 8)
|
||||
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_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_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_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_prev)
|
||||
@ -125,6 +130,7 @@ class StructTest(unittest.TestCase):
|
||||
self.s.append_field(self.f_testint)
|
||||
self.s.append_field(self.f_testfloat)
|
||||
self.s.append_field(self.f_testulong)
|
||||
self.s.append_field(self.f_substruct)
|
||||
|
||||
def test_autosize(self):
|
||||
with self.assertRaises(ValueError):
|
||||
@ -263,6 +269,19 @@ class StructTest(unittest.TestCase):
|
||||
self.assertAlmostEqual(2.79, val[1])
|
||||
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):
|
||||
fileobj = mock.MagicMock(io.BufferedReader)
|
||||
value = 255
|
||||
@ -344,3 +363,10 @@ class StructTest(unittest.TestCase):
|
||||
expected = struct.pack(b">f", value)
|
||||
self.s.field_set(self.FakeHeader(), fileobj, b"testfloat", value)
|
||||
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)
|
||||
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):
|
||||
ob = self.bf.code_index[b"OB"][0]
|
||||
assert isinstance(ob, blendfile.BlendFileBlock)
|
||||
@ -297,6 +303,29 @@ class ArrayTest(AbstractBlendFileTest):
|
||||
self.assertEqual(name, tex.id_name)
|
||||
|
||||
|
||||
class DynamicArrayTest(AbstractBlendFileTest):
|
||||
def test_dynamic_array_of_bakes(self):
|
||||
self.bf = blendfile.BlendFile(self.blendfiles / "multiple_geometry_nodes_bakes.blend")
|
||||
obj = self.bf.code_index[b"OB"][0]
|
||||
assert isinstance(obj, blendfile.BlendFileBlock)
|
||||
modifier = obj.get_pointer((b"modifiers", b"first"))
|
||||
assert isinstance(modifier, blendfile.BlendFileBlock)
|
||||
bakes = modifier.get_pointer(b"bakes")
|
||||
|
||||
bake_count = bakes.count
|
||||
self.assertEqual(3, bake_count)
|
||||
|
||||
for i, bake in enumerate(blendfile.iterators.dynamic_array(bakes)):
|
||||
if i == 0:
|
||||
frame_start = 37
|
||||
if i == 1:
|
||||
frame_start = 5
|
||||
if i == 2:
|
||||
frame_start = 12
|
||||
|
||||
self.assertEqual(frame_start, bake.get(b"frame_start"))
|
||||
|
||||
|
||||
class CompressionRecognitionTest(AbstractBlendFileTest):
|
||||
def _find_compression_type(self, filename: str) -> magic_compression.Compression:
|
||||
path = self.blendfiles / filename
|
||||
|
||||
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):
|
||||
def test_abort_strategise(self):
|
||||
infile = self.blendfiles / "subdir/doubly_linked_up.blend"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import collections
|
||||
import functools
|
||||
import logging
|
||||
import sys
|
||||
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):
|
||||
self.assert_deps(
|
||||
"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):
|
||||
infinite_bfile = self.blendfiles / "recursive_dependency_1.blend"
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user