Added block walker implementations + tests
This commit is contained in:
parent
6b9c0a3f95
commit
65b690e998
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@ __pycache__
|
|||||||
/.cache
|
/.cache
|
||||||
/.pytest_cache
|
/.pytest_cache
|
||||||
/.coverage
|
/.coverage
|
||||||
|
|
||||||
|
/tests/blendfiles/cache_ocean/
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
from . import BlendFileBlock
|
from . import BlendFileBlock
|
||||||
|
from .dna import FieldPath
|
||||||
|
|
||||||
|
|
||||||
def listbase(block: BlendFileBlock) -> BlendFileBlock:
|
def listbase(block: BlendFileBlock, next_path: FieldPath=b'next') -> BlendFileBlock:
|
||||||
"""Generator, yields all blocks in the ListBase linked list."""
|
"""Generator, yields all blocks in the ListBase linked list."""
|
||||||
while block:
|
while block:
|
||||||
yield block
|
yield block
|
||||||
next_ptr = block[b'next']
|
next_ptr = block[next_path]
|
||||||
block = block.bfile.find_block_from_address(next_ptr)
|
block = block.bfile.find_block_from_address(next_ptr)
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import logging
|
|||||||
import pathlib
|
import pathlib
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from blender_asset_tracer import bpathlib, blendfile
|
from blender_asset_tracer import blendfile
|
||||||
from . import result, blocks
|
from . import result, block_walkers
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ def deps(bfilepath: pathlib.Path) -> typing.Iterator[result.BlockUsage]:
|
|||||||
|
|
||||||
with blendfile.BlendFile(bfilepath) as bfile:
|
with blendfile.BlendFile(bfilepath) as bfile:
|
||||||
for block in asset_holding_blocks(bfile):
|
for block in asset_holding_blocks(bfile):
|
||||||
yield from blocks.from_block(block)
|
yield from block_walkers.from_block(block)
|
||||||
|
|
||||||
# TODO: handle library blocks for recursion.
|
# TODO: handle library blocks for recursion.
|
||||||
|
|
||||||
|
|||||||
174
blender_asset_tracer/tracer/block_walkers.py
Normal file
174
blender_asset_tracer/tracer/block_walkers.py
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
"""Block walkers.
|
||||||
|
|
||||||
|
From a BlendFileBlock, the block walker functions yield BlockUsage objects.
|
||||||
|
The top-level block walkers are implemented as _from_block_XX() function,
|
||||||
|
where XX is the DNA code of the block.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from blender_asset_tracer import blendfile, bpathlib
|
||||||
|
from blender_asset_tracer.blendfile import iterators
|
||||||
|
from . import result, cdefs
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_warned_about_types = set()
|
||||||
|
|
||||||
|
|
||||||
|
def from_block(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
assert block.code != b'DATA'
|
||||||
|
|
||||||
|
module = sys.modules[__name__]
|
||||||
|
funcname = '_from_block_' + block.code.decode().lower()
|
||||||
|
try:
|
||||||
|
block_reader = getattr(module, funcname)
|
||||||
|
except AttributeError:
|
||||||
|
if block.code not in _warned_about_types:
|
||||||
|
log.warning('No reader implemented for block type %r', block.code.decode())
|
||||||
|
_warned_about_types.add(block.code)
|
||||||
|
return
|
||||||
|
|
||||||
|
log.debug('Tracing block %r', block)
|
||||||
|
yield from block_reader(block)
|
||||||
|
|
||||||
|
|
||||||
|
def skip_packed(wrapped):
|
||||||
|
"""Decorator, skip blocks where 'packedfile' is set to true."""
|
||||||
|
|
||||||
|
@functools.wraps(wrapped)
|
||||||
|
def wrapper(block: blendfile.BlendFileBlock, *args, **kwargs):
|
||||||
|
if block.get(b'packedfile', default=False):
|
||||||
|
return
|
||||||
|
|
||||||
|
yield from wrapped(block, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def _from_block_cf(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Cache file data blocks."""
|
||||||
|
path, field = block.get(b'filepath', return_field=True)
|
||||||
|
yield result.BlockUsage(block, path, path_full_field=field)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_packed
|
||||||
|
def _from_block_im(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Image data blocks."""
|
||||||
|
# old files miss this
|
||||||
|
image_source = block.get(b'source', default=cdefs.IMA_SRC_FILE)
|
||||||
|
if image_source not in {cdefs.IMA_SRC_FILE, cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_MOVIE}:
|
||||||
|
return
|
||||||
|
|
||||||
|
pathname, field = block.get(b'name', return_field=True)
|
||||||
|
is_sequence = image_source == cdefs.IMA_SRC_SEQUENCE
|
||||||
|
|
||||||
|
yield result.BlockUsage(block, pathname, is_sequence, path_full_field=field)
|
||||||
|
|
||||||
|
|
||||||
|
def _from_block_me(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Mesh data blocks."""
|
||||||
|
block_external = block.get_pointer((b'ldata', b'external'), None)
|
||||||
|
if block_external is None:
|
||||||
|
block_external = block.get_pointer((b'fdata', b'external'), None)
|
||||||
|
if block_external is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
path, field = block_external.get(b'filename', return_field=True)
|
||||||
|
yield result.BlockUsage(block, path, path_full_field=field)
|
||||||
|
|
||||||
|
|
||||||
|
def _from_block_mc(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""MovieClip data blocks."""
|
||||||
|
path, field = block.get(b'name', return_field=True)
|
||||||
|
# TODO: The assumption that this is not a sequence may not be true for all modifiers.
|
||||||
|
yield result.BlockUsage(block, path, is_sequence=False, path_full_field=field)
|
||||||
|
|
||||||
|
|
||||||
|
def _from_block_ob(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Object data blocks."""
|
||||||
|
# 'ob->modifiers[...].filepath'
|
||||||
|
ob_idname = block[b'id', b'name']
|
||||||
|
mods = block.get_pointer((b'modifiers', b'first'))
|
||||||
|
for mod_idx, block_mod in enumerate(iterators.listbase(mods, next_path=(b'modifier', b'next'))):
|
||||||
|
block_name = b'%s.modifiers[%d]' % (ob_idname, mod_idx)
|
||||||
|
log.debug('Tracing modifier %s', block_name.decode())
|
||||||
|
|
||||||
|
mod_type = block_mod[b'modifier', b'type']
|
||||||
|
if mod_type == cdefs.eModifierType_Ocean:
|
||||||
|
if block_mod[b'cached']:
|
||||||
|
path, field = block_mod.get(b'cachepath', return_field=True)
|
||||||
|
# The path indicates the directory containing the cached files.
|
||||||
|
yield result.BlockUsage(block_mod, path, is_sequence=True, path_full_field=field,
|
||||||
|
block_name=block_name)
|
||||||
|
|
||||||
|
elif mod_type == cdefs.eModifierType_MeshCache:
|
||||||
|
path, field = block_mod.get(b'filepath', return_field=True)
|
||||||
|
yield result.BlockUsage(block_mod, path, is_sequence=False, path_full_field=field,
|
||||||
|
block_name=block_name)
|
||||||
|
|
||||||
|
|
||||||
|
def _from_block_sc(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Scene data blocks."""
|
||||||
|
# Sequence editor is the only interesting bit.
|
||||||
|
block_ed = block.get_pointer(b'ed')
|
||||||
|
if block_ed is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
single_asset_types = {cdefs.SEQ_TYPE_MOVIE, cdefs.SEQ_TYPE_SOUND_RAM, cdefs.SEQ_TYPE_SOUND_HD}
|
||||||
|
asset_types = single_asset_types.union({cdefs.SEQ_TYPE_IMAGE})
|
||||||
|
|
||||||
|
def iter_seqbase(seqbase) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Generate results from a ListBase of sequencer strips."""
|
||||||
|
|
||||||
|
for seq in iterators.listbase(seqbase):
|
||||||
|
seq.refine_type(b'Sequence')
|
||||||
|
seq_type = seq[b'type']
|
||||||
|
|
||||||
|
if seq_type == cdefs.SEQ_TYPE_META:
|
||||||
|
# Recurse into this meta-sequence.
|
||||||
|
subseq = seq.get_pointer((b'seqbase', b'first'))
|
||||||
|
yield from iter_seqbase(subseq)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if seq_type not in asset_types:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seq_strip = seq.get_pointer(b'strip')
|
||||||
|
if seq_strip is None:
|
||||||
|
continue
|
||||||
|
seq_stripdata = seq_strip.get_pointer(b'stripdata')
|
||||||
|
if seq_stripdata is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
dirname, dn_field = seq_strip.get(b'dir', return_field=True)
|
||||||
|
basename, bn_field = seq_stripdata.get(b'name', return_field=True)
|
||||||
|
asset_path = bpathlib.BlendPath(dirname) / basename
|
||||||
|
|
||||||
|
is_sequence = seq_type not in single_asset_types
|
||||||
|
yield result.BlockUsage(seq, asset_path,
|
||||||
|
is_sequence=is_sequence,
|
||||||
|
path_dir_field=dn_field,
|
||||||
|
path_base_field=bn_field)
|
||||||
|
|
||||||
|
sbase = block_ed.get_pointer((b'seqbase', b'first'))
|
||||||
|
yield from iter_seqbase(sbase)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_packed
|
||||||
|
def _from_block_so(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Sound data blocks."""
|
||||||
|
path, field = block.get(b'name', return_field=True)
|
||||||
|
yield result.BlockUsage(block, path, path_full_field=field)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_packed
|
||||||
|
def _from_block_vf(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||||
|
"""Vector Font data blocks."""
|
||||||
|
path, field = block.get(b'name', return_field=True)
|
||||||
|
if path == b'<builtin>': # builtin font
|
||||||
|
return
|
||||||
|
yield result.BlockUsage(block, path, path_full_field=field)
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
import typing
|
|
||||||
|
|
||||||
from blender_asset_tracer import blendfile
|
|
||||||
from . import result, cdefs
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
_warned_about_types = set()
|
|
||||||
|
|
||||||
|
|
||||||
class NoReaderImplemented(NotImplementedError):
|
|
||||||
"""There is no reader implementation for a specific block code."""
|
|
||||||
|
|
||||||
def __init__(self, message: str, code: bytes):
|
|
||||||
super().__init__(message)
|
|
||||||
self.code = code
|
|
||||||
|
|
||||||
|
|
||||||
def from_block(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
|
||||||
assert block.code != b'DATA'
|
|
||||||
|
|
||||||
module = sys.modules[__name__]
|
|
||||||
funcname = '_from_block_' + block.code.decode().lower()
|
|
||||||
try:
|
|
||||||
block_reader = getattr(module, funcname)
|
|
||||||
except AttributeError:
|
|
||||||
if block.code not in _warned_about_types:
|
|
||||||
log.warning('No reader implemented for block type %r', block.code.decode())
|
|
||||||
_warned_about_types.add(block.code)
|
|
||||||
return
|
|
||||||
|
|
||||||
yield from block_reader(block)
|
|
||||||
|
|
||||||
|
|
||||||
def _from_block_im(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
|
||||||
# old files miss this
|
|
||||||
image_source = block.get(b'source', default=cdefs.IMA_SRC_FILE)
|
|
||||||
if image_source not in {cdefs.IMA_SRC_FILE, cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_MOVIE}:
|
|
||||||
return
|
|
||||||
if block[b'packedfile']:
|
|
||||||
return
|
|
||||||
|
|
||||||
pathname, field = block.get(b'name', return_field=True)
|
|
||||||
|
|
||||||
# TODO: the receiver should inspect the 'source' property too, and if set
|
|
||||||
# to cdefs.IMA_SRC_SEQUENCE yield the entire sequence of files.
|
|
||||||
yield result.BlockUsage(block, field, pathname)
|
|
||||||
@ -34,6 +34,7 @@ IMA_SRC_SEQUENCE = 2
|
|||||||
IMA_SRC_MOVIE = 3
|
IMA_SRC_MOVIE = 3
|
||||||
|
|
||||||
# DNA_modifier_types.h
|
# DNA_modifier_types.h
|
||||||
|
eModifierType_Ocean = 39
|
||||||
eModifierType_MeshCache = 46
|
eModifierType_MeshCache = 46
|
||||||
|
|
||||||
# DNA_particle_types.h
|
# DNA_particle_types.h
|
||||||
|
|||||||
@ -1,35 +1,73 @@
|
|||||||
|
from blender_asset_tracer import blendfile, bpathlib
|
||||||
|
from blender_asset_tracer.blendfile import dna
|
||||||
|
|
||||||
|
|
||||||
class BlockUsage:
|
class BlockUsage:
|
||||||
"""Represents the use of an asset by a data block.
|
"""Represents the use of an asset by a data block.
|
||||||
|
|
||||||
|
:ivar block_name: an identifying name for this block. Defaults to the ID
|
||||||
|
name of the block.
|
||||||
:ivar block:
|
:ivar block:
|
||||||
:type block: blendfile.BlendFileBlock
|
|
||||||
:ivar field:
|
|
||||||
:type field: dna.Field
|
|
||||||
:ivar asset_path:
|
:ivar asset_path:
|
||||||
:type asset_path: bpathlib.BlendPath
|
:ivar is_sequence: Indicates whether this file is alone (False), the
|
||||||
|
first of a sequence (True, and the path points to a file), or a
|
||||||
|
directory containing a sequence (True, and path points to a directory).
|
||||||
|
In certain cases such files should be reported once (f.e. when
|
||||||
|
rewriting the source field to another path), and in other cases the
|
||||||
|
sequence should be expanded (f.e. when copying all assets to a BAT
|
||||||
|
Pack).
|
||||||
|
:ivar path_full_field: field containing the full path of this asset.
|
||||||
|
:ivar path_dir_field: field containing the parent path (i.e. the
|
||||||
|
directory) of this asset.
|
||||||
|
:ivar path_base_field: field containing the basename of this asset.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, block, field, asset_path):
|
def __init__(self,
|
||||||
self.block_idname = block[b'id', b'name']
|
block: blendfile.BlendFileBlock,
|
||||||
|
asset_path: bpathlib.BlendPath,
|
||||||
from blender_asset_tracer import blendfile, bpathlib
|
is_sequence: bool = False,
|
||||||
from blender_asset_tracer.blendfile import dna
|
path_full_field: dna.Field = None,
|
||||||
|
path_dir_field: dna.Field = None,
|
||||||
|
path_base_field: dna.Field = None,
|
||||||
|
block_name: bytes = '',
|
||||||
|
):
|
||||||
|
if block_name:
|
||||||
|
self.block_name = block_name
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.block_name = block[b'id', b'name']
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
self.block_name = block[b'name']
|
||||||
|
except KeyError:
|
||||||
|
self.block_name = b'-unnamed-'
|
||||||
|
|
||||||
assert isinstance(block, blendfile.BlendFileBlock)
|
assert isinstance(block, blendfile.BlendFileBlock)
|
||||||
assert isinstance(field, dna.Field), 'field should be dna.Field, not %r' % type(field)
|
|
||||||
assert isinstance(asset_path, (bytes, bpathlib.BlendPath)), \
|
assert isinstance(asset_path, (bytes, bpathlib.BlendPath)), \
|
||||||
'asset_path should be BlendPath, not %r' % type(asset_path)
|
'asset_path should be BlendPath, not %r' % type(asset_path)
|
||||||
|
|
||||||
|
if path_full_field is None:
|
||||||
|
assert isinstance(path_dir_field, dna.Field), \
|
||||||
|
'path_dir_field should be dna.Field, not %r' % type(path_dir_field)
|
||||||
|
assert isinstance(path_base_field, dna.Field), \
|
||||||
|
'path_base_field should be dna.Field, not %r' % type(path_base_field)
|
||||||
|
else:
|
||||||
|
assert isinstance(path_full_field, dna.Field), \
|
||||||
|
'path_full_field should be dna.Field, not %r' % type(path_full_field)
|
||||||
|
|
||||||
if isinstance(asset_path, bytes):
|
if isinstance(asset_path, bytes):
|
||||||
asset_path = bpathlib.BlendPath(asset_path)
|
asset_path = bpathlib.BlendPath(asset_path)
|
||||||
|
|
||||||
self.block = block
|
self.block = block
|
||||||
self.field = field
|
|
||||||
self.asset_path = asset_path
|
self.asset_path = asset_path
|
||||||
|
self.is_sequence = bool(is_sequence)
|
||||||
|
self.path_full_field = path_full_field
|
||||||
|
self.path_dir_field = path_dir_field
|
||||||
|
self.path_base_field = path_base_field
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<BlockUsage name=%r type=%r field=%r asset=%r>' % (
|
return '<BlockUsage name=%r type=%r field=%r asset=%r%s>' % (
|
||||||
self.block_idname, self.block.dna_type_name,
|
self.block_name, self.block.dna_type_name,
|
||||||
self.field.name.name_full.decode(), self.asset_path)
|
self.path_full_field.name.name_full.decode(), self.asset_path,
|
||||||
|
' sequence' if self.is_sequence else ''
|
||||||
|
)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ class AbstractBlendFileTest(unittest.TestCase):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.blendfiles = pathlib.Path(__file__).with_name('blendfiles')
|
cls.blendfiles = pathlib.Path(__file__).with_name('blendfiles')
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.bf = None
|
self.bf = None
|
||||||
|
|||||||
BIN
tests/blendfiles/Cube.btx
Normal file
BIN
tests/blendfiles/Cube.btx
Normal file
Binary file not shown.
BIN
tests/blendfiles/alembic-source.blend
Normal file
BIN
tests/blendfiles/alembic-source.blend
Normal file
Binary file not shown.
BIN
tests/blendfiles/alembic-user.blend
Normal file
BIN
tests/blendfiles/alembic-user.blend
Normal file
Binary file not shown.
BIN
tests/blendfiles/clothsim.abc
Normal file
BIN
tests/blendfiles/clothsim.abc
Normal file
Binary file not shown.
BIN
tests/blendfiles/image_sequencer.blend
Normal file
BIN
tests/blendfiles/image_sequencer.blend
Normal file
Binary file not shown.
BIN
tests/blendfiles/imgseq/000210.png
Normal file
BIN
tests/blendfiles/imgseq/000210.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
tests/blendfiles/imgseq/000211.png
Normal file
BIN
tests/blendfiles/imgseq/000211.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
tests/blendfiles/imgseq/000212.png
Normal file
BIN
tests/blendfiles/imgseq/000212.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
tests/blendfiles/imgseq/000213.png
Normal file
BIN
tests/blendfiles/imgseq/000213.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
tests/blendfiles/imgseq/000214.png
Normal file
BIN
tests/blendfiles/imgseq/000214.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
3
tests/blendfiles/imgseq/LICENSE.txt
Normal file
3
tests/blendfiles/imgseq/LICENSE.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
License: CC-BY
|
||||||
|
Created by: Blender Animation Studio
|
||||||
|
https://cloud.blender.org/p/dailydweebs/
|
||||||
Binary file not shown.
BIN
tests/blendfiles/meshcache-source.blend
Normal file
BIN
tests/blendfiles/meshcache-source.blend
Normal file
Binary file not shown.
BIN
tests/blendfiles/meshcache-user.blend
Normal file
BIN
tests/blendfiles/meshcache-user.blend
Normal file
Binary file not shown.
BIN
tests/blendfiles/meshcache.mdd
Normal file
BIN
tests/blendfiles/meshcache.mdd
Normal file
Binary file not shown.
BIN
tests/blendfiles/movieclip.blend
Normal file
BIN
tests/blendfiles/movieclip.blend
Normal file
Binary file not shown.
BIN
tests/blendfiles/multires_external.blend
Normal file
BIN
tests/blendfiles/multires_external.blend
Normal file
Binary file not shown.
BIN
tests/blendfiles/ocean_modifier.blend
Normal file
BIN
tests/blendfiles/ocean_modifier.blend
Normal file
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 11 MiB |
BIN
tests/blendfiles/with_font.blend
Normal file
BIN
tests/blendfiles/with_font.blend
Normal file
Binary file not shown.
@ -1,15 +1,19 @@
|
|||||||
|
import collections
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from blender_asset_tracer import tracer, blendfile
|
||||||
from abstract_test import AbstractBlendFileTest
|
from abstract_test import AbstractBlendFileTest
|
||||||
|
|
||||||
from blender_asset_tracer import tracer, blendfile
|
# Mimicks a BlockUsage, but without having to set the block to an expected value.
|
||||||
|
Expect = collections.namedtuple(
|
||||||
|
'Expect',
|
||||||
|
'type full_field dirname_field basename_field asset_path is_sequence')
|
||||||
|
|
||||||
|
|
||||||
class AbstractTracerTest(AbstractBlendFileTest):
|
class AbstractTracerTest(AbstractBlendFileTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
super().setUpClass()
|
super().setUpClass()
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
logging.getLogger('blender_asset_tracer.tracer').setLevel(logging.DEBUG)
|
logging.getLogger('blender_asset_tracer.tracer').setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
@ -56,11 +60,97 @@ class AssetHoldingBlocksTest(AbstractTracerTest):
|
|||||||
self.assertEqual(37, blocks_seen)
|
self.assertEqual(37, blocks_seen)
|
||||||
|
|
||||||
|
|
||||||
class DepsTest(AbstractBlendFileTest):
|
class DepsTest(AbstractTracerTest):
|
||||||
|
|
||||||
|
def assert_deps(self, blend_fname, expects):
|
||||||
|
for dep in tracer.deps(self.blendfiles / blend_fname):
|
||||||
|
exp = expects[dep.block_name]
|
||||||
|
self.assertEqual(exp.type, dep.block.dna_type.dna_type_id.decode())
|
||||||
|
self.assertEqual(exp.asset_path, dep.asset_path)
|
||||||
|
self.assertEqual(exp.is_sequence, dep.is_sequence)
|
||||||
|
|
||||||
|
if exp.full_field is not None:
|
||||||
|
self.assertEqual(exp.full_field, dep.path_full_field.name.name_full.decode())
|
||||||
|
if exp.dirname_field is not None:
|
||||||
|
self.assertEqual(exp.dirname_field, dep.path_dir_field.name.name_full.decode())
|
||||||
|
if exp.basename_field is not None:
|
||||||
|
self.assertEqual(exp.basename_field, dep.path_base_field.name.name_full.decode())
|
||||||
|
|
||||||
|
del expects[dep.block_name] # should be seen only once
|
||||||
|
# All expected uses should have been seen.
|
||||||
|
self.assertEqual({}, expects)
|
||||||
|
|
||||||
def test_no_deps(self):
|
def test_no_deps(self):
|
||||||
for dep in tracer.deps(self.blendfiles / 'basic_file.blend'):
|
self.assert_deps('basic_file.blend', {})
|
||||||
self.fail(dep)
|
|
||||||
|
|
||||||
def test_ob_mat_texture(self):
|
def test_ob_mat_texture(self):
|
||||||
for dep in tracer.deps(self.blendfiles / 'material_textures.blend'):
|
expects = {
|
||||||
self.fail(repr(dep))
|
b'IMbrick_dotted_04-bump': Expect(
|
||||||
|
'Image', 'name[1024]', None, None,
|
||||||
|
b'//textures/Bricks/brick_dotted_04-bump.jpg', False),
|
||||||
|
b'IMbrick_dotted_04-color': Expect(
|
||||||
|
'Image', 'name[1024]', None, None,
|
||||||
|
b'//textures/Bricks/brick_dotted_04-color.jpg', False),
|
||||||
|
# This data block is in there, but the image is packed, so it
|
||||||
|
# shouldn't be in the results.
|
||||||
|
# b'IMbrick_dotted_04-specular': Expect(
|
||||||
|
# 'Image', 'name[1024]', None, None,
|
||||||
|
# b'//textures/Bricks/brick_dotted_04-specular.jpg', False),
|
||||||
|
b'IMbuildings_roof_04-color': Expect(
|
||||||
|
'Image', 'name[1024]', None, None,
|
||||||
|
b'//textures/Textures/Buildings/buildings_roof_04-color.png', False),
|
||||||
|
}
|
||||||
|
self.assert_deps('material_textures.blend', expects)
|
||||||
|
|
||||||
|
def test_seq_image_sequence(self):
|
||||||
|
expects = {
|
||||||
|
b'SQ000210.png': Expect(
|
||||||
|
'Sequence', None, 'dir[768]', 'name[256]', b'//imgseq/000210.png', True),
|
||||||
|
b'SQvideo-tiny.mkv': Expect(
|
||||||
|
'Sequence', None, 'dir[768]', 'name[256]',
|
||||||
|
b'//../../../../cloud/pillar/testfiles/video-tiny.mkv', False),
|
||||||
|
|
||||||
|
# The sound will be referenced twice, from the sequence strip and an SO data block.
|
||||||
|
b'SQvideo-tiny.001': Expect(
|
||||||
|
'Sequence', None, 'dir[768]', 'name[256]',
|
||||||
|
b'//../../../../cloud/pillar/testfiles/video-tiny.mkv', False),
|
||||||
|
b'SOvideo-tiny.mkv': Expect(
|
||||||
|
'bSound', 'name[1024]', None, None,
|
||||||
|
b'//../../../../cloud/pillar/testfiles/video-tiny.mkv', False),
|
||||||
|
}
|
||||||
|
self.assert_deps('image_sequencer.blend', expects)
|
||||||
|
|
||||||
|
def test_block_cf(self):
|
||||||
|
self.assert_deps('alembic-user.blend', {
|
||||||
|
b'CFclothsim.abc': Expect('CacheFile', 'filepath[1024]', None, None,
|
||||||
|
b'//clothsim.abc', False),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_block_mc(self):
|
||||||
|
self.assert_deps('movieclip.blend', {
|
||||||
|
b'MCvideo.mov': Expect('MovieClip', 'name[1024]', None, None,
|
||||||
|
b'//../../../../cloud/pillar/testfiles/video.mov', False),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_block_me(self):
|
||||||
|
self.assert_deps('multires_external.blend', {
|
||||||
|
b'MECube': Expect('Mesh', 'filename[1024]', None, None, b'//Cube.btx', False),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_ocean(self):
|
||||||
|
self.assert_deps('ocean_modifier.blend', {
|
||||||
|
b'OBPlane.modifiers[0]': Expect('OceanModifierData', 'cachepath[1024]', None, None,
|
||||||
|
b'//cache_ocean', True),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_mesh_cache(self):
|
||||||
|
self.assert_deps('meshcache-user.blend', {
|
||||||
|
b'OBPlane.modifiers[0]': Expect('MeshCacheModifierData', 'filepath[1024]', None, None,
|
||||||
|
b'//meshcache.mdd', False),
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_block_vf(self):
|
||||||
|
self.assert_deps('with_font.blend', {
|
||||||
|
b'VFHack-Bold': Expect('VFont', 'name[1024]', None, None,
|
||||||
|
b'/usr/share/fonts/truetype/hack/Hack-Bold.ttf', False),
|
||||||
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user