---
The blendfile module within BAT supports reading data from structs, such
as the Count property on an array modifier. However, blendfile only
supports modifying structs with type "char". This patch adds support for
writing structs of more types in blendfile blocks. Now, writing is
supported for ushort, short, uint, int, float, and ulong types.
The use case that inspired this patch was an instance where a file had
several array modifiers that prevented the file from being opened in
Blender on machines without large amounts of RAM. A solution using the
blendfile module may look like:
```
from blender_asset_tracer import blendfile
from pathlib import Path
b = blendfile.open_cached(Path('flag.blend'), mode='rb+')
for block in b.blocks:
if 'ArrayModifierData' in block.__str__():
try:
print('previous:', block.get(b'count'))
block.set(b'count', 1)
print('current:', block.get(b'count'))
except KeyError:
continue
b.close()
```
This would fail with the exception
`blender_asset_tracer.blendfile.exceptions.NoWriterImplemented: Setting
type Struct(b'int') is not supported for ArrayModifierData.count`. With
this patch, the above code succeeds and the count struct can be set to
a lower number that allows the file to be opened.
This solution implements missing functionality without adding any new
interfaces. A few details are:
* When deciding what type to write to the struct, the value is inferred
from what is given by the caller. If the caller gives a Python int, the
exact type is inferred from the DNA type ID. If they give a float, a
float is written. Otherwise, the existing logic is used to determine
whether to write a string or byte sequence.
* A \_write method was added to dna\_io.py that takes a Python struct
object and a value to write a byte sequence to the file object. This
method is used by public methods appropriately named to indicate what
type they will write.
* The check for whether the caller is trying to write an unsupported
type is left in place, but it has been changed to include types which
are now supported.
* Tests have been added that provide a mock file object, call the new
methods, and confirm that the correct bytes were written.
Reviewed By: sybren
Differential Revision: https://developer.blender.org/D14374
Blender Asset Tracer BAT🦇
Script to manage assets with Blender.
Blender Asset Tracer, a.k.a. BAT🦇, is the replacement of BAM and blender-file
Development is driven by choices explained in T54125.
Setting up development environment
python3.9 -m venv .venv
. ./.venv/bin/activate
pip install -U pip
pip install poetry black
poetry install
mypy --install-types
Uploading to S3-compatible storage
BAT Pack supports uploading to S3-compatible storage. This requires a credentials file in
~/.aws/credentials. Replace the all-capital words to suit your situation.
[ENDPOINT]
aws_access_key_id = YOUR_ACCESS_KEY_ID
aws_secret_access_key = YOUR_SECRET
You can then send a BAT Pack to the storage using a target s3:/ENDPOINT/bucketname/path-in-bucket,
for example:
bat pack my_blendfile.blend s3:/storage.service.cloud/jobs/awesome_work
This will upload the blend file and its dependencies to awesome_work/my_blendfile.blend in
the jobs bucket.
Paths
There are two object types used to represent file paths. Those are strictly separated.
bpathlib.BlendPathrepresents a path as stored in a blend file. It consists of bytes, and is blendfile-relative when it starts with//. It can represent any path from any OS Blender supports, and as such should be used carefully.pathlib.Pathrepresents an actual path, possibly on the local filesystem of the computer running BAT. Any filesystem operation (such as checking whether it exists) must be done using apathlib.Path.
When it is necessary to interpret a bpathlib.BlendPath as a real path instead of a sequence of
bytes, BAT first attempts to decode it as UTF-8. If that fails, the local filesystem encoding is
used. The latter is also no guarantee of correctness, though.
Type checking
The code statically type-checked with mypy.
Mypy likes to see the return type of __init__ methods explicitly declared as None. Until issue
#604 is resolved, we just do this in our code too.
Code Example
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:
#!/usr/bin/env python3.7
import json
import sys
from pathlib import Path
from blender_asset_tracer import blendfile
from blender_asset_tracer.blendfile import iterators
if len(sys.argv) != 2:
print(f'Usage: {sys.argv[0]} somefile.blend', file=sys.stderr)
sys.exit(1)
bf_path = Path(sys.argv[1])
bf = blendfile.open_cached(bf_path)
# Get the first window manager (there is probably exactly one).
window_managers = bf.find_blocks_from_code(b'WM')
assert window_managers, 'The Blend file has no window manager'
window_manager = window_managers[0]
# Get the scene from the first window.
windows = window_manager.get_pointer((b'windows', b'first'))
for window in iterators.listbase(windows):
scene = window.get_pointer(b'scene')
break
# BAT can only return simple values, so it can't return the embedded
# struct 'r'. 'r.engine' is a simple string, though.
engine = scene[b'r', b'engine'].decode('utf8')
xsch = scene[b'r', b'xsch']
ysch = scene[b'r', b'ysch']
size = scene[b'r', b'size'] / 100.0
render_info = {
'engine': engine,
'frame_pixels': {
'x': int(xsch * size),
'y': int(ysch * size),
},
}
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
mapping to the names accessible in Blender's Python interface can be found in the rna_yyyy.c
files.
Code Guidelines
This section documents some guidelines for the code in BAT.
Avoiding Late Imports
All imports should be done at the top level of the file, and not inside functions. The goal is to ensure that, once imported, a (sub)module of BAT can be used without having to import more parts of BAT.
This requirement helps to keep Blender add-ons separated, as an add-on can
import the modules of BAT it needs, then remove them from sys.modules and
sys.path so that other add-ons don't see them. This should reduce problems
with various add-ons shipping different versions of BAT.