Archived
update BAT module to 1.15 version
This commit is contained in:
+14
-10
@@ -28,28 +28,31 @@ log = logging.getLogger(__name__)
|
||||
|
||||
codes_to_skip = {
|
||||
# These blocks never have external assets:
|
||||
b'ID', b'WM', b'SN',
|
||||
|
||||
b"ID",
|
||||
b"WM",
|
||||
b"SN",
|
||||
# These blocks are skipped for now, until we have proof they point to
|
||||
# assets otherwise missed:
|
||||
b'GR', b'WO', b'BR', b'LS',
|
||||
b"GR",
|
||||
b"WO",
|
||||
b"BR",
|
||||
b"LS",
|
||||
}
|
||||
|
||||
|
||||
def deps(bfilepath: pathlib.Path, progress_cb: typing.Optional[progress.Callback] = None) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
def deps(
|
||||
bfilepath: pathlib.Path, progress_cb: typing.Optional[progress.Callback] = None
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Open the blend file and report its dependencies.
|
||||
|
||||
:param bfilepath: File to open.
|
||||
:param progress_cb: Progress callback object.
|
||||
"""
|
||||
|
||||
log.info('opening: %s', bfilepath)
|
||||
bfile = blendfile.open_cached(bfilepath)
|
||||
|
||||
bi = file2blocks.BlockIterator()
|
||||
if progress_cb:
|
||||
bi.progress_cb = progress_cb
|
||||
bfile = bi.open_blendfile(bfilepath)
|
||||
|
||||
# Remember which block usages we've reported already, without keeping the
|
||||
# blocks themselves in memory.
|
||||
@@ -64,8 +67,9 @@ def deps(bfilepath: pathlib.Path, progress_cb: typing.Optional[progress.Callback
|
||||
yield block_usage
|
||||
|
||||
|
||||
def asset_holding_blocks(blocks: typing.Iterable[blendfile.BlendFileBlock]) \
|
||||
-> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
def asset_holding_blocks(
|
||||
blocks: typing.Iterable[blendfile.BlendFileBlock],
|
||||
) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
"""Generator, yield data blocks that could reference external assets."""
|
||||
for block in blocks:
|
||||
assert isinstance(block, blendfile.BlendFileBlock)
|
||||
|
||||
+53
-48
@@ -39,17 +39,17 @@ _funcs_for_code = {} # type: typing.Dict[bytes, typing.Callable]
|
||||
|
||||
def iter_assets(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Generator, yield the assets used by this data block."""
|
||||
assert block.code != b'DATA'
|
||||
assert block.code != b"DATA"
|
||||
|
||||
try:
|
||||
block_reader = _funcs_for_code[block.code]
|
||||
except KeyError:
|
||||
if block.code not in _warned_about_types:
|
||||
log.debug('No reader implemented for block type %r', block.code.decode())
|
||||
log.debug("No reader implemented for block type %r", block.code.decode())
|
||||
_warned_about_types.add(block.code)
|
||||
return
|
||||
|
||||
log.debug('Tracing block %r', block)
|
||||
log.debug("Tracing block %r", block)
|
||||
yield from block_reader(block)
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ def skip_packed(wrapped):
|
||||
|
||||
@functools.wraps(wrapped)
|
||||
def wrapper(block: blendfile.BlendFileBlock, *args, **kwargs):
|
||||
if block.get(b'packedfile', default=False):
|
||||
log.debug('Datablock %r is packed; skipping', block.id_name)
|
||||
if block.get(b"packedfile", default=False):
|
||||
log.debug("Datablock %r is packed; skipping", block.id_name)
|
||||
return
|
||||
|
||||
yield from wrapped(block, *args, **kwargs)
|
||||
@@ -79,40 +79,38 @@ def skip_packed(wrapped):
|
||||
return wrapper
|
||||
|
||||
|
||||
@dna_code('CF')
|
||||
@dna_code("CF")
|
||||
def cache_file(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Cache file data blocks."""
|
||||
path, field = block.get(b'filepath', return_field=True)
|
||||
path, field = block.get(b"filepath", return_field=True)
|
||||
yield result.BlockUsage(block, path, path_full_field=field)
|
||||
|
||||
|
||||
@dna_code('IM')
|
||||
@dna_code("IM")
|
||||
@skip_packed
|
||||
def image(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)
|
||||
#print('------image_source: ', image_source)
|
||||
#if image_source not in {cdefs.IMA_SRC_FILE, cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_MOVIE}:
|
||||
# return
|
||||
if image_source not in {cdefs.IMA_SRC_FILE, cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_MOVIE, cdefs.IMA_SRC_TILED}:
|
||||
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,
|
||||
cdefs.IMA_SRC_TILED,
|
||||
}:
|
||||
log.debug("skiping image source type %s", image_source)
|
||||
return
|
||||
pathname, field = block.get(b'name', return_field=True)
|
||||
#is_sequence = image_source == cdefs.IMA_SRC_SEQUENCE
|
||||
|
||||
if image_source in {cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_TILED}:
|
||||
is_sequence = True
|
||||
else:
|
||||
is_sequence = False
|
||||
pathname, field = block.get(b"name", return_field=True)
|
||||
is_sequence = image_source in {cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_TILED}
|
||||
|
||||
#print('is_sequence: ', is_sequence)
|
||||
yield result.BlockUsage(block, pathname, is_sequence, path_full_field=field)
|
||||
|
||||
|
||||
@dna_code('LI')
|
||||
@dna_code("LI")
|
||||
def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Library data blocks."""
|
||||
path, field = block.get(b'name', return_field=True)
|
||||
path, field = block.get(b"name", return_field=True)
|
||||
yield result.BlockUsage(block, path, path_full_field=field)
|
||||
|
||||
# The 'filepath' also points to the blend file. However, this is set to the
|
||||
@@ -120,37 +118,37 @@ def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsag
|
||||
# is thus not a property we have to report or rewrite.
|
||||
|
||||
|
||||
@dna_code('ME')
|
||||
@dna_code("ME")
|
||||
def mesh(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Mesh data blocks."""
|
||||
block_external = block.get_pointer((b'ldata', b'external'), None)
|
||||
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)
|
||||
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)
|
||||
path, field = block_external.get(b"filename", return_field=True)
|
||||
yield result.BlockUsage(block, path, path_full_field=field)
|
||||
|
||||
|
||||
@dna_code('MC')
|
||||
@dna_code("MC")
|
||||
def movie_clip(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""MovieClip data blocks."""
|
||||
path, field = block.get(b'name', return_field=True)
|
||||
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)
|
||||
|
||||
|
||||
@dna_code('OB')
|
||||
@dna_code("OB")
|
||||
def object_block(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Object data blocks."""
|
||||
ctx = modifier_walkers.ModifierContext(owner=block)
|
||||
|
||||
# 'ob->modifiers[...].filepath'
|
||||
for mod_idx, block_mod in enumerate(iterators.modifiers(block)):
|
||||
block_name = b'%s.modifiers[%d]' % (block.id_name, mod_idx)
|
||||
mod_type = block_mod[b'modifier', b'type']
|
||||
log.debug('Tracing modifier %s, type=%d', block_name.decode(), mod_type)
|
||||
block_name = b"%s.modifiers[%d]" % (block.id_name, mod_idx)
|
||||
mod_type = block_mod[b"modifier", b"type"]
|
||||
log.debug("Tracing modifier %s, type=%d", block_name.decode(), mod_type)
|
||||
|
||||
try:
|
||||
mod_handler = modifier_walkers.modifier_handlers[mod_type]
|
||||
@@ -159,52 +157,59 @@ def object_block(block: blendfile.BlendFileBlock) -> typing.Iterator[result.Bloc
|
||||
yield from mod_handler(ctx, block_mod, block_name)
|
||||
|
||||
|
||||
@dna_code('SC')
|
||||
@dna_code("SC")
|
||||
def scene(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Scene data blocks."""
|
||||
# Sequence editor is the only interesting bit.
|
||||
block_ed = block.get_pointer(b'ed')
|
||||
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}
|
||||
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})
|
||||
|
||||
for seq, seq_type in iterators.sequencer_strips(block_ed):
|
||||
if seq_type not in asset_types:
|
||||
continue
|
||||
|
||||
seq_strip = seq.get_pointer(b'strip')
|
||||
seq_strip = seq.get_pointer(b"strip")
|
||||
if seq_strip is None:
|
||||
continue
|
||||
seq_stripdata = seq_strip.get_pointer(b'stripdata')
|
||||
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)
|
||||
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_strip, asset_path,
|
||||
is_sequence=is_sequence,
|
||||
path_dir_field=dn_field,
|
||||
path_base_field=bn_field)
|
||||
yield result.BlockUsage(
|
||||
seq_strip,
|
||||
asset_path,
|
||||
is_sequence=is_sequence,
|
||||
path_dir_field=dn_field,
|
||||
path_base_field=bn_field,
|
||||
)
|
||||
|
||||
|
||||
@dna_code('SO')
|
||||
@dna_code("SO")
|
||||
@skip_packed
|
||||
def sound(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Sound data blocks."""
|
||||
path, field = block.get(b'name', return_field=True)
|
||||
path, field = block.get(b"name", return_field=True)
|
||||
yield result.BlockUsage(block, path, path_full_field=field)
|
||||
|
||||
|
||||
@dna_code('VF')
|
||||
@dna_code("VF")
|
||||
@skip_packed
|
||||
def vector_font(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
|
||||
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)
|
||||
|
||||
+137
-74
@@ -30,26 +30,36 @@ from blender_asset_tracer import blendfile, cdefs
|
||||
from blender_asset_tracer.blendfile import iterators
|
||||
|
||||
# Don't warn about these types at all.
|
||||
_warned_about_types = {b'LI', b'DATA'}
|
||||
_warned_about_types = {b"LI", b"DATA"}
|
||||
_funcs_for_code = {} # type: typing.Dict[bytes, typing.Callable]
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def expand_block(block: blendfile.BlendFileBlock) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
def expand_block(
|
||||
block: blendfile.BlendFileBlock,
|
||||
) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
"""Generator, yield the data blocks used by this data block."""
|
||||
|
||||
try:
|
||||
expander = _funcs_for_code[block.code]
|
||||
except KeyError:
|
||||
if block.code not in _warned_about_types:
|
||||
log.debug('No expander implemented for block type %r', block.code.decode())
|
||||
log.debug("No expander implemented for block type %r", block.code.decode())
|
||||
_warned_about_types.add(block.code)
|
||||
return
|
||||
|
||||
log.debug('Expanding block %r', block)
|
||||
# Filter out falsy blocks, i.e. None values.
|
||||
# Allowing expanders to yield None makes them more consise.
|
||||
yield from filter(None, expander(block))
|
||||
log.debug("Expanding block %r", block)
|
||||
for dependency in expander(block):
|
||||
if not dependency:
|
||||
# Filter out falsy blocks, i.e. None values.
|
||||
# Allowing expanders to yield None makes them more consise.
|
||||
continue
|
||||
if dependency.code == b"DATA":
|
||||
log.warn(
|
||||
"expander yielded block %s which will be ignored in later iteration",
|
||||
dependency,
|
||||
)
|
||||
yield dependency
|
||||
|
||||
|
||||
def dna_code(block_code: str):
|
||||
@@ -65,83 +75,131 @@ def dna_code(block_code: str):
|
||||
|
||||
|
||||
def _expand_generic_material(block: blendfile.BlendFileBlock):
|
||||
array_len = block.get(b'totcol')
|
||||
yield from block.iter_array_of_pointers(b'mat', array_len)
|
||||
array_len = block.get(b"totcol")
|
||||
yield from block.iter_array_of_pointers(b"mat", array_len)
|
||||
|
||||
|
||||
def _expand_generic_mtex(block: blendfile.BlendFileBlock):
|
||||
if not block.dna_type.has_field(b'mtex'):
|
||||
if not block.dna_type.has_field(b"mtex"):
|
||||
# mtex was removed in Blender 2.8
|
||||
return
|
||||
|
||||
for mtex in block.iter_fixed_array_of_pointers(b'mtex'):
|
||||
yield mtex.get_pointer(b'tex')
|
||||
yield mtex.get_pointer(b'object')
|
||||
for mtex in block.iter_fixed_array_of_pointers(b"mtex"):
|
||||
yield mtex.get_pointer(b"tex")
|
||||
yield mtex.get_pointer(b"object")
|
||||
|
||||
|
||||
def _expand_generic_nodetree(block: blendfile.BlendFileBlock):
|
||||
assert block.dna_type.dna_type_id == b'bNodeTree'
|
||||
assert block.dna_type.dna_type_id == b"bNodeTree"
|
||||
|
||||
nodes = block.get_pointer((b"nodes", b"first"))
|
||||
|
||||
# See DNA_node_types.h
|
||||
socket_types_with_value_pointer = {
|
||||
cdefs.SOCK_OBJECT, # bNodeSocketValueObject
|
||||
cdefs.SOCK_IMAGE, # bNodeSocketValueImage
|
||||
cdefs.SOCK_COLLECTION, # bNodeSocketValueCollection
|
||||
cdefs.SOCK_TEXTURE, # bNodeSocketValueTexture
|
||||
cdefs.SOCK_MATERIAL, # bNodeSocketValueMaterial
|
||||
}
|
||||
|
||||
nodes = block.get_pointer((b'nodes', b'first'))
|
||||
for node in iterators.listbase(nodes):
|
||||
if node[b'type'] == cdefs.CMP_NODE_R_LAYERS:
|
||||
if node[b"type"] == cdefs.CMP_NODE_R_LAYERS:
|
||||
continue
|
||||
yield node
|
||||
|
||||
# The 'id' property points to whatever is used by the node
|
||||
# (like the image in an image texture node).
|
||||
yield node.get_pointer(b'id')
|
||||
yield node.get_pointer(b"id")
|
||||
|
||||
# Default values of inputs can also point to ID datablocks.
|
||||
inputs = node.get_pointer((b"inputs", b"first"))
|
||||
for input in iterators.listbase(inputs):
|
||||
if input[b"type"] not in socket_types_with_value_pointer:
|
||||
continue
|
||||
value_container = input.get_pointer(b"default_value")
|
||||
if not value_container:
|
||||
continue
|
||||
value = value_container.get_pointer(b"value")
|
||||
yield value
|
||||
|
||||
|
||||
def _expand_generic_idprops(block: blendfile.BlendFileBlock):
|
||||
"""Yield ID datablocks and their libraries referenced from ID properties."""
|
||||
|
||||
# TODO(@sybren): this code is very crude, and happens to work on ID
|
||||
# properties of Geometry Nodes modifiers, which is what it was written for.
|
||||
# It should probably be rewritten to properly iterate over & recurse into
|
||||
# all groups.
|
||||
settings_props = block.get_pointer((b"settings", b"properties"))
|
||||
if not settings_props:
|
||||
return
|
||||
|
||||
subprops = settings_props.get_pointer((b"data", b"group", b"first"))
|
||||
for idprop in iterators.listbase(subprops):
|
||||
if idprop[b"type"] != cdefs.IDP_ID:
|
||||
continue
|
||||
id_datablock = idprop.get_pointer((b"data", b"pointer"))
|
||||
if not id_datablock:
|
||||
continue
|
||||
yield id_datablock
|
||||
|
||||
|
||||
def _expand_generic_nodetree_id(block: blendfile.BlendFileBlock):
|
||||
block_ntree = block.get_pointer(b'nodetree', None)
|
||||
block_ntree = block.get_pointer(b"nodetree", None)
|
||||
if block_ntree is not None:
|
||||
yield from _expand_generic_nodetree(block_ntree)
|
||||
|
||||
|
||||
def _expand_generic_animdata(block: blendfile.BlendFileBlock):
|
||||
block_adt = block.get_pointer(b'adt')
|
||||
block_adt = block.get_pointer(b"adt")
|
||||
if block_adt:
|
||||
yield block_adt.get_pointer(b'action')
|
||||
yield block_adt.get_pointer(b"action")
|
||||
# TODO, NLA
|
||||
|
||||
|
||||
@dna_code('AR')
|
||||
@dna_code("AR")
|
||||
def _expand_armature(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
|
||||
|
||||
@dna_code('CU')
|
||||
@dna_code("CU")
|
||||
def _expand_curve(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_material(block)
|
||||
|
||||
for fieldname in (b'vfont', b'vfontb', b'vfonti', b'vfontbi',
|
||||
b'bevobj', b'taperobj', b'textoncurve'):
|
||||
for fieldname in (
|
||||
b"vfont",
|
||||
b"vfontb",
|
||||
b"vfonti",
|
||||
b"vfontbi",
|
||||
b"bevobj",
|
||||
b"taperobj",
|
||||
b"textoncurve",
|
||||
):
|
||||
yield block.get_pointer(fieldname)
|
||||
|
||||
|
||||
@dna_code('GR')
|
||||
@dna_code("GR")
|
||||
def _expand_group(block: blendfile.BlendFileBlock):
|
||||
log.debug('Collection/group Block: %s (name=%s)', block, block.id_name)
|
||||
log.debug("Collection/group Block: %s (name=%s)", block, block.id_name)
|
||||
|
||||
objects = block.get_pointer((b'gobject', b'first'))
|
||||
objects = block.get_pointer((b"gobject", b"first"))
|
||||
for item in iterators.listbase(objects):
|
||||
yield item.get_pointer(b'ob')
|
||||
yield item.get_pointer(b"ob")
|
||||
|
||||
# Recurse through child collections.
|
||||
try:
|
||||
children = block.get_pointer((b'children', b'first'))
|
||||
children = block.get_pointer((b"children", b"first"))
|
||||
except KeyError:
|
||||
# 'children' was introduced in Blender 2.8 collections
|
||||
pass
|
||||
else:
|
||||
for child in iterators.listbase(children):
|
||||
subcoll = child.get_pointer(b'collection')
|
||||
subcoll = child.get_pointer(b"collection")
|
||||
if subcoll is None:
|
||||
continue
|
||||
|
||||
if subcoll.dna_type_id == b'ID':
|
||||
if subcoll.dna_type_id == b"ID":
|
||||
# This issue happened while recursing a linked-in 'Hidden'
|
||||
# collection in the Chimes set of the Spring project. Such
|
||||
# collections named 'Hidden' were apparently created while
|
||||
@@ -150,127 +208,132 @@ def _expand_group(block: blendfile.BlendFileBlock):
|
||||
yield subcoll
|
||||
continue
|
||||
|
||||
log.debug('recursing into child collection %s (name=%r, type=%r)',
|
||||
subcoll, subcoll.id_name, subcoll.dna_type_name)
|
||||
log.debug(
|
||||
"recursing into child collection %s (name=%r, type=%r)",
|
||||
subcoll,
|
||||
subcoll.id_name,
|
||||
subcoll.dna_type_name,
|
||||
)
|
||||
yield from _expand_group(subcoll)
|
||||
|
||||
|
||||
@dna_code('LA')
|
||||
@dna_code("LA")
|
||||
def _expand_lamp(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_nodetree_id(block)
|
||||
yield from _expand_generic_mtex(block)
|
||||
|
||||
|
||||
@dna_code('MA')
|
||||
@dna_code("MA")
|
||||
def _expand_material(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_nodetree_id(block)
|
||||
yield from _expand_generic_mtex(block)
|
||||
|
||||
try:
|
||||
yield block.get_pointer(b'group')
|
||||
yield block.get_pointer(b"group")
|
||||
except KeyError:
|
||||
# Groups were removed from Blender 2.8
|
||||
pass
|
||||
|
||||
|
||||
@dna_code('MB')
|
||||
@dna_code("MB")
|
||||
def _expand_metaball(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_material(block)
|
||||
|
||||
|
||||
@dna_code('ME')
|
||||
@dna_code("ME")
|
||||
def _expand_mesh(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_material(block)
|
||||
yield block.get_pointer(b'texcomesh')
|
||||
yield block.get_pointer(b"texcomesh")
|
||||
# TODO, TexFace? - it will be slow, we could simply ignore :S
|
||||
|
||||
|
||||
@dna_code('NT')
|
||||
@dna_code("NT")
|
||||
def _expand_node_tree(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_nodetree(block)
|
||||
|
||||
|
||||
@dna_code('OB')
|
||||
@dna_code("OB")
|
||||
def _expand_object(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_material(block)
|
||||
|
||||
yield block.get_pointer(b'data')
|
||||
yield block.get_pointer(b"data")
|
||||
|
||||
if block[b'transflag'] & cdefs.OB_DUPLIGROUP:
|
||||
yield block.get_pointer(b'dup_group')
|
||||
if block[b"transflag"] & cdefs.OB_DUPLIGROUP:
|
||||
yield block.get_pointer(b"dup_group")
|
||||
|
||||
yield block.get_pointer(b'proxy')
|
||||
yield block.get_pointer(b'proxy_group')
|
||||
yield block.get_pointer(b"proxy")
|
||||
yield block.get_pointer(b"proxy_group")
|
||||
|
||||
# 'ob->pose->chanbase[...].custom'
|
||||
block_pose = block.get_pointer(b'pose')
|
||||
block_pose = block.get_pointer(b"pose")
|
||||
if block_pose:
|
||||
assert block_pose.dna_type.dna_type_id == b'bPose'
|
||||
assert block_pose.dna_type.dna_type_id == b"bPose"
|
||||
# sdna_index_bPoseChannel = block_pose.file.sdna_index_from_id[b'bPoseChannel']
|
||||
channels = block_pose.get_pointer((b'chanbase', b'first'))
|
||||
channels = block_pose.get_pointer((b"chanbase", b"first"))
|
||||
for pose_chan in iterators.listbase(channels):
|
||||
yield pose_chan.get_pointer(b'custom')
|
||||
yield pose_chan.get_pointer(b"custom")
|
||||
|
||||
# Expand the objects 'ParticleSettings' via 'ob->particlesystem[...].part'
|
||||
# sdna_index_ParticleSystem = block.file.sdna_index_from_id.get(b'ParticleSystem')
|
||||
# if sdna_index_ParticleSystem is not None:
|
||||
psystems = block.get_pointer((b'particlesystem', b'first'))
|
||||
psystems = block.get_pointer((b"particlesystem", b"first"))
|
||||
for psystem in iterators.listbase(psystems):
|
||||
yield psystem.get_pointer(b'part')
|
||||
yield psystem.get_pointer(b"part")
|
||||
|
||||
# Modifiers can also refer to other datablocks, which should also get expanded.
|
||||
for block_mod in iterators.modifiers(block):
|
||||
mod_type = block_mod[b'modifier', b'type']
|
||||
mod_type = block_mod[b"modifier", b"type"]
|
||||
# Currently only node groups are supported. If the support should expand
|
||||
# to more types, something more intelligent than this should be made.
|
||||
if mod_type == cdefs.eModifierType_Nodes:
|
||||
yield block_mod.get_pointer(b'node_group')
|
||||
yield from _expand_generic_idprops(block_mod)
|
||||
yield block_mod.get_pointer(b"node_group")
|
||||
|
||||
|
||||
@dna_code('PA')
|
||||
@dna_code("PA")
|
||||
def _expand_particle_settings(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_mtex(block)
|
||||
|
||||
block_ren_as = block[b'ren_as']
|
||||
block_ren_as = block[b"ren_as"]
|
||||
if block_ren_as == cdefs.PART_DRAW_GR:
|
||||
yield block.get_pointer(b'dup_group')
|
||||
yield block.get_pointer(b"dup_group")
|
||||
elif block_ren_as == cdefs.PART_DRAW_OB:
|
||||
yield block.get_pointer(b'dup_ob')
|
||||
yield block.get_pointer(b"dup_ob")
|
||||
|
||||
|
||||
@dna_code('SC')
|
||||
@dna_code("SC")
|
||||
def _expand_scene(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_nodetree_id(block)
|
||||
yield block.get_pointer(b'camera')
|
||||
yield block.get_pointer(b'world')
|
||||
yield block.get_pointer(b'set', default=None)
|
||||
yield block.get_pointer(b'clip', default=None)
|
||||
yield block.get_pointer(b"camera")
|
||||
yield block.get_pointer(b"world")
|
||||
yield block.get_pointer(b"set", default=None)
|
||||
yield block.get_pointer(b"clip", default=None)
|
||||
|
||||
# sdna_index_Base = block.file.sdna_index_from_id[b'Base']
|
||||
# for item in bf_utils.iter_ListBase(block.get_pointer((b'base', b'first'))):
|
||||
# yield item.get_pointer(b'object', sdna_index_refine=sdna_index_Base)
|
||||
bases = block.get_pointer((b'base', b'first'))
|
||||
bases = block.get_pointer((b"base", b"first"))
|
||||
for base in iterators.listbase(bases):
|
||||
yield base.get_pointer(b'object')
|
||||
yield base.get_pointer(b"object")
|
||||
|
||||
# Sequence Editor
|
||||
block_ed = block.get_pointer(b'ed')
|
||||
block_ed = block.get_pointer(b"ed")
|
||||
if not block_ed:
|
||||
return
|
||||
|
||||
strip_type_to_field = {
|
||||
cdefs.SEQ_TYPE_SCENE: b'scene',
|
||||
cdefs.SEQ_TYPE_MOVIECLIP: b'clip',
|
||||
cdefs.SEQ_TYPE_MASK: b'mask',
|
||||
cdefs.SEQ_TYPE_SOUND_RAM: b'sound',
|
||||
cdefs.SEQ_TYPE_SCENE: b"scene",
|
||||
cdefs.SEQ_TYPE_MOVIECLIP: b"clip",
|
||||
cdefs.SEQ_TYPE_MASK: b"mask",
|
||||
cdefs.SEQ_TYPE_SOUND_RAM: b"sound",
|
||||
}
|
||||
for strip, strip_type in iterators.sequencer_strips(block_ed):
|
||||
try:
|
||||
@@ -280,14 +343,14 @@ def _expand_scene(block: blendfile.BlendFileBlock):
|
||||
yield strip.get_pointer(field_name)
|
||||
|
||||
|
||||
@dna_code('TE')
|
||||
@dna_code("TE")
|
||||
def _expand_texture(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_nodetree_id(block)
|
||||
yield block.get_pointer(b'ima')
|
||||
yield block.get_pointer(b"ima")
|
||||
|
||||
|
||||
@dna_code('WO')
|
||||
@dna_code("WO")
|
||||
def _expand_world(block: blendfile.BlendFileBlock):
|
||||
yield from _expand_generic_animdata(block)
|
||||
yield from _expand_generic_nodetree_id(block)
|
||||
|
||||
+33
-28
@@ -65,14 +65,21 @@ class BlockIterator:
|
||||
|
||||
self.progress_cb = progress.Callback()
|
||||
|
||||
def iter_blocks(self,
|
||||
bfile: blendfile.BlendFile,
|
||||
limit_to: typing.Set[blendfile.BlendFileBlock] = set(),
|
||||
) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
def open_blendfile(self, bfilepath: pathlib.Path) -> blendfile.BlendFile:
|
||||
"""Open a blend file, sending notification about this to the progress callback."""
|
||||
|
||||
log.info("opening: %s", bfilepath)
|
||||
self.progress_cb.trace_blendfile(bfilepath)
|
||||
return blendfile.open_cached(bfilepath)
|
||||
|
||||
def iter_blocks(
|
||||
self,
|
||||
bfile: blendfile.BlendFile,
|
||||
limit_to: typing.Set[blendfile.BlendFileBlock] = set(),
|
||||
) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
"""Expand blocks with dependencies from other libraries."""
|
||||
|
||||
self.progress_cb.trace_blendfile(bfile.filepath)
|
||||
log.info('inspecting: %s', bfile.filepath)
|
||||
log.info("inspecting: %s", bfile.filepath)
|
||||
if limit_to:
|
||||
self._queue_named_blocks(bfile, limit_to)
|
||||
else:
|
||||
@@ -94,14 +101,14 @@ class BlockIterator:
|
||||
if (bpath, block.addr_old) in self.blocks_yielded:
|
||||
continue
|
||||
|
||||
if block.code == b'ID':
|
||||
if block.code == b"ID":
|
||||
# ID blocks represent linked-in assets. Those are the ones that
|
||||
# should be loaded from their own blend file and "expanded" to
|
||||
# the entire set of data blocks required to render them. We
|
||||
# defer the handling of those so that we can work with one
|
||||
# blend file at a time.
|
||||
lib = block.get_pointer(b'lib')
|
||||
lib_bpath = bpathlib.BlendPath(lib[b'name']).absolute(root_dir)
|
||||
lib = block.get_pointer(b"lib")
|
||||
lib_bpath = bpathlib.BlendPath(lib[b"name"]).absolute(root_dir)
|
||||
blocks_per_lib[lib_bpath].add(block)
|
||||
|
||||
# The library block itself should also be reported, because it
|
||||
@@ -109,11 +116,7 @@ class BlockIterator:
|
||||
self.to_visit.put(lib)
|
||||
continue
|
||||
|
||||
if limit_to:
|
||||
# We're limiting the blocks, so we have to expand them to make
|
||||
# sure we don't miss anything. Otherwise we're yielding the
|
||||
# entire file anyway, and no expansion is necessary.
|
||||
self._queue_dependencies(block)
|
||||
self._queue_dependencies(block)
|
||||
self.blocks_yielded.add((bpath, block.addr_old))
|
||||
yield block
|
||||
|
||||
@@ -125,27 +128,26 @@ class BlockIterator:
|
||||
for lib_bpath, idblocks in blocks_per_lib.items():
|
||||
lib_path = bpathlib.make_absolute(lib_bpath.to_path())
|
||||
|
||||
#assert lib_path.exists()
|
||||
if not lib_path.exists():
|
||||
log.warning('Library %s does not exist', lib_path)
|
||||
log.warning("Library %s does not exist", lib_path)
|
||||
continue
|
||||
|
||||
log.debug('Expanding %d blocks in %s', len(idblocks), lib_path)
|
||||
libfile = blendfile.open_cached(lib_path)
|
||||
log.debug("Expanding %d blocks in %s", len(idblocks), lib_path)
|
||||
libfile = self.open_blendfile(lib_path)
|
||||
yield from self.iter_blocks(libfile, idblocks)
|
||||
|
||||
def _queue_all_blocks(self, bfile: blendfile.BlendFile):
|
||||
log.debug('Queueing all blocks from file %s', bfile.filepath)
|
||||
log.debug("Queueing all blocks from file %s", bfile.filepath)
|
||||
for block in bfile.blocks:
|
||||
# Don't bother visiting DATA blocks, as we won't know what
|
||||
# to do with them anyway.
|
||||
if block.code == b'DATA':
|
||||
if block.code == b"DATA":
|
||||
continue
|
||||
self.to_visit.put(block)
|
||||
|
||||
def _queue_named_blocks(self,
|
||||
bfile: blendfile.BlendFile,
|
||||
limit_to: typing.Set[blendfile.BlendFileBlock]):
|
||||
def _queue_named_blocks(
|
||||
self, bfile: blendfile.BlendFile, limit_to: typing.Set[blendfile.BlendFileBlock]
|
||||
):
|
||||
"""Queue only the blocks referred to in limit_to.
|
||||
|
||||
:param bfile:
|
||||
@@ -155,22 +157,25 @@ class BlockIterator:
|
||||
"""
|
||||
|
||||
for to_find in limit_to:
|
||||
assert to_find.code == b'ID'
|
||||
name_to_find = to_find[b'name']
|
||||
assert to_find.code == b"ID"
|
||||
name_to_find = to_find[b"name"]
|
||||
code = name_to_find[:2]
|
||||
log.debug('Finding block %r with code %r', name_to_find, code)
|
||||
log.debug("Finding block %r with code %r", name_to_find, code)
|
||||
same_code = bfile.find_blocks_from_code(code)
|
||||
for block in same_code:
|
||||
if block.id_name == name_to_find:
|
||||
log.debug('Queueing %r from file %s', block, bfile.filepath)
|
||||
log.debug("Queueing %r from file %s", block, bfile.filepath)
|
||||
self.to_visit.put(block)
|
||||
|
||||
def _queue_dependencies(self, block: blendfile.BlendFileBlock):
|
||||
for block in expanders.expand_block(block):
|
||||
assert isinstance(block, blendfile.BlendFileBlock), "unexpected %r" % block
|
||||
self.to_visit.put(block)
|
||||
|
||||
|
||||
def iter_blocks(bfile: blendfile.BlendFile) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
def iter_blocks(
|
||||
bfile: blendfile.BlendFile,
|
||||
) -> typing.Iterator[blendfile.BlendFileBlock]:
|
||||
"""Generator, yield all blocks in this file + required blocks in libs."""
|
||||
bi = BlockIterator()
|
||||
yield from bi.iter_blocks(bfile)
|
||||
|
||||
+12
-5
@@ -39,13 +39,19 @@ def expand_sequence(path: pathlib.Path) -> typing.Iterator[pathlib.Path]:
|
||||
or the path of the first file in the sequence.
|
||||
"""
|
||||
|
||||
if '*' in str(path): # assume it is a glob
|
||||
if "<UDIM>" in path.name: # UDIM tiles
|
||||
# Change <UDIM> marker to a glob pattern, then let the glob case handle it.
|
||||
# This assumes that all files that match the glob are actually UDIM
|
||||
# tiles; this could cause some false-positives.
|
||||
path = path.with_name(path.name.replace("<UDIM>", "*"))
|
||||
|
||||
if "*" in str(path): # assume it is a glob
|
||||
import glob
|
||||
log.debug('expanding glob %s', path)
|
||||
|
||||
log.debug("expanding glob %s", path)
|
||||
for fname in sorted(glob.glob(str(path), recursive=True)):
|
||||
yield pathlib.Path(fname)
|
||||
return
|
||||
|
||||
if not path.exists():
|
||||
raise DoesNotExist(path)
|
||||
|
||||
@@ -53,9 +59,10 @@ def expand_sequence(path: pathlib.Path) -> typing.Iterator[pathlib.Path]:
|
||||
yield path
|
||||
return
|
||||
|
||||
log.debug('expanding file sequence %s', path)
|
||||
log.debug("expanding file sequence %s", path)
|
||||
|
||||
import string
|
||||
|
||||
stem_no_digits = path.stem.rstrip(string.digits)
|
||||
if stem_no_digits == path.stem:
|
||||
# Just a single file, no digits here.
|
||||
@@ -65,5 +72,5 @@ def expand_sequence(path: pathlib.Path) -> typing.Iterator[pathlib.Path]:
|
||||
# Return everything start starts with 'stem_no_digits' and ends with the
|
||||
# same suffix as the first file. This may result in more files than used
|
||||
# by Blender, but at least it shouldn't miss any.
|
||||
pattern = '%s*%s' % (stem_no_digits, path.suffix)
|
||||
pattern = "%s*%s" % (stem_no_digits, path.suffix)
|
||||
yield from sorted(path.parent.glob(pattern))
|
||||
|
||||
+152
-84
@@ -37,8 +37,9 @@ class ModifierContext:
|
||||
|
||||
Currently just contains the object on which the modifier is defined.
|
||||
"""
|
||||
|
||||
def __init__(self, owner: blendfile.BlendFileBlock) -> None:
|
||||
assert owner.dna_type_name == 'Object'
|
||||
assert owner.dna_type_name == "Object"
|
||||
self.owner = owner
|
||||
|
||||
|
||||
@@ -55,45 +56,56 @@ def mod_handler(dna_num: int):
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_MeshCache)
|
||||
def modifier_filepath(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
def modifier_filepath(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Just yield the 'filepath' field."""
|
||||
path, field = modifier.get(b'filepath', return_field=True)
|
||||
yield result.BlockUsage(modifier, path, path_full_field=field, block_name=block_name)
|
||||
path, field = modifier.get(b"filepath", return_field=True)
|
||||
yield result.BlockUsage(
|
||||
modifier, path, path_full_field=field, block_name=block_name
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_MeshSequenceCache)
|
||||
def modifier_mesh_sequence_cache(ctx: ModifierContext, modifier: blendfile.BlendFileBlock,
|
||||
block_name: bytes) -> typing.Iterator[result.BlockUsage]:
|
||||
def modifier_mesh_sequence_cache(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Yield the Alembic file(s) used by this modifier"""
|
||||
cache_file = modifier.get_pointer(b'cache_file')
|
||||
cache_file = modifier.get_pointer(b"cache_file")
|
||||
if cache_file is None:
|
||||
return
|
||||
|
||||
is_sequence = bool(cache_file[b'is_sequence'])
|
||||
is_sequence = bool(cache_file[b"is_sequence"])
|
||||
cache_block_name = cache_file.id_name
|
||||
assert cache_block_name is not None
|
||||
|
||||
path, field = cache_file.get(b'filepath', return_field=True)
|
||||
yield result.BlockUsage(cache_file, path, path_full_field=field,
|
||||
is_sequence=is_sequence,
|
||||
block_name=cache_block_name)
|
||||
path, field = cache_file.get(b"filepath", return_field=True)
|
||||
yield result.BlockUsage(
|
||||
cache_file,
|
||||
path,
|
||||
path_full_field=field,
|
||||
is_sequence=is_sequence,
|
||||
block_name=cache_block_name,
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_Ocean)
|
||||
def modifier_ocean(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
if not modifier[b'cached']:
|
||||
def modifier_ocean(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
if not modifier[b"cached"]:
|
||||
return
|
||||
|
||||
path, field = modifier.get(b'cachepath', return_field=True)
|
||||
path, field = modifier.get(b"cachepath", return_field=True)
|
||||
# The path indicates the directory containing the cached files.
|
||||
yield result.BlockUsage(modifier, path, is_sequence=True, path_full_field=field,
|
||||
block_name=block_name)
|
||||
yield result.BlockUsage(
|
||||
modifier, path, is_sequence=True, path_full_field=field, block_name=block_name
|
||||
)
|
||||
|
||||
|
||||
def _get_texture(prop_name: bytes, dblock: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
def _get_texture(
|
||||
prop_name: bytes, dblock: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Yield block usages from a texture propery.
|
||||
|
||||
Assumes dblock[prop_name] is a texture data block.
|
||||
@@ -102,13 +114,14 @@ def _get_texture(prop_name: bytes, dblock: blendfile.BlendFileBlock, block_name:
|
||||
return
|
||||
|
||||
tx = dblock.get_pointer(prop_name)
|
||||
yield from _get_image(b'ima', tx, block_name)
|
||||
yield from _get_image(b"ima", tx, block_name)
|
||||
|
||||
|
||||
def _get_image(prop_name: bytes,
|
||||
dblock: typing.Optional[blendfile.BlendFileBlock],
|
||||
block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
def _get_image(
|
||||
prop_name: bytes,
|
||||
dblock: typing.Optional[blendfile.BlendFileBlock],
|
||||
block_name: bytes,
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
"""Yield block usages from an image propery.
|
||||
|
||||
Assumes dblock[prop_name] is an image data block.
|
||||
@@ -120,132 +133,187 @@ def _get_image(prop_name: bytes,
|
||||
ima = dblock.get_pointer(prop_name)
|
||||
except KeyError as ex:
|
||||
# No such property, just return.
|
||||
log.debug('_get_image() called with non-existing property name: %s', ex)
|
||||
log.debug("_get_image() called with non-existing property name: %s", ex)
|
||||
return
|
||||
|
||||
if not ima:
|
||||
return
|
||||
|
||||
path, field = ima.get(b'name', return_field=True)
|
||||
path, field = ima.get(b"name", return_field=True)
|
||||
yield result.BlockUsage(ima, path, path_full_field=field, block_name=block_name)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_Displace)
|
||||
@mod_handler(cdefs.eModifierType_Wave)
|
||||
def modifier_texture(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
return _get_texture(b'texture', modifier, block_name)
|
||||
def modifier_texture(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
return _get_texture(b"texture", modifier, block_name)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_WeightVGEdit)
|
||||
@mod_handler(cdefs.eModifierType_WeightVGMix)
|
||||
@mod_handler(cdefs.eModifierType_WeightVGProximity)
|
||||
def modifier_mask_texture(ctx: ModifierContext, modifier: blendfile.BlendFileBlock,
|
||||
block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
return _get_texture(b'mask_texture', modifier, block_name)
|
||||
def modifier_mask_texture(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
return _get_texture(b"mask_texture", modifier, block_name)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_UVProject)
|
||||
def modifier_image(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
yield from _get_image(b'image', modifier, block_name)
|
||||
def modifier_image(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
yield from _get_image(b"image", modifier, block_name)
|
||||
|
||||
|
||||
def _walk_point_cache(ctx: ModifierContext,
|
||||
block_name: bytes,
|
||||
bfile: blendfile.BlendFile,
|
||||
pointcache: blendfile.BlendFileBlock,
|
||||
extension: bytes):
|
||||
flag = pointcache[b'flag']
|
||||
def _walk_point_cache(
|
||||
ctx: ModifierContext,
|
||||
block_name: bytes,
|
||||
bfile: blendfile.BlendFile,
|
||||
pointcache: blendfile.BlendFileBlock,
|
||||
extension: bytes,
|
||||
):
|
||||
flag = pointcache[b"flag"]
|
||||
if flag & cdefs.PTCACHE_EXTERNAL:
|
||||
path, field = pointcache.get(b'path', return_field=True)
|
||||
log.info(' external cache at %s', path)
|
||||
path, field = pointcache.get(b"path", return_field=True)
|
||||
log.info(" external cache at %s", path)
|
||||
bpath = bpathlib.BlendPath(path)
|
||||
yield result.BlockUsage(pointcache, bpath, path_full_field=field,
|
||||
is_sequence=True, block_name=block_name)
|
||||
yield result.BlockUsage(
|
||||
pointcache,
|
||||
bpath,
|
||||
path_full_field=field,
|
||||
is_sequence=True,
|
||||
block_name=block_name,
|
||||
)
|
||||
elif flag & cdefs.PTCACHE_DISK_CACHE:
|
||||
# See ptcache_path() in pointcache.c
|
||||
name, field = pointcache.get(b'name', return_field=True)
|
||||
name, field = pointcache.get(b"name", return_field=True)
|
||||
if not name:
|
||||
# See ptcache_filename() in pointcache.c
|
||||
idname = ctx.owner[b'id', b'name']
|
||||
idname = ctx.owner[b"id", b"name"]
|
||||
name = idname[2:].hex().upper().encode()
|
||||
path = b'//%b%b/%b_*%b' % (
|
||||
path = b"//%b%b/%b_*%b" % (
|
||||
cdefs.PTCACHE_PATH,
|
||||
bfile.filepath.stem.encode(),
|
||||
name,
|
||||
extension)
|
||||
log.info(' disk cache at %s', path)
|
||||
extension,
|
||||
)
|
||||
log.info(" disk cache at %s", path)
|
||||
bpath = bpathlib.BlendPath(path)
|
||||
yield result.BlockUsage(pointcache, bpath, path_full_field=field,
|
||||
is_sequence=True, block_name=block_name)
|
||||
yield result.BlockUsage(
|
||||
pointcache,
|
||||
bpath,
|
||||
path_full_field=field,
|
||||
is_sequence=True,
|
||||
block_name=block_name,
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_ParticleSystem)
|
||||
def modifier_particle_system(ctx: ModifierContext, modifier: blendfile.BlendFileBlock,
|
||||
block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
psys = modifier.get_pointer(b'psys')
|
||||
def modifier_particle_system(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
psys = modifier.get_pointer(b"psys")
|
||||
if psys is None:
|
||||
return
|
||||
|
||||
pointcache = psys.get_pointer(b'pointcache')
|
||||
pointcache = psys.get_pointer(b"pointcache")
|
||||
if pointcache is None:
|
||||
return
|
||||
|
||||
yield from _walk_point_cache(ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT)
|
||||
yield from _walk_point_cache(
|
||||
ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_Fluidsim)
|
||||
def modifier_fluid_sim(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
my_log = log.getChild('modifier_fluid_sim')
|
||||
def modifier_fluid_sim(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
my_log = log.getChild("modifier_fluid_sim")
|
||||
|
||||
fss = modifier.get_pointer(b'fss')
|
||||
fss = modifier.get_pointer(b"fss")
|
||||
if fss is None:
|
||||
my_log.debug('Modifier %r (%r) has no fss',
|
||||
modifier[b'modifier', b'name'], block_name)
|
||||
my_log.debug(
|
||||
"Modifier %r (%r) has no fss", modifier[b"modifier", b"name"], block_name
|
||||
)
|
||||
return
|
||||
|
||||
path, field = fss.get(b'surfdataPath', return_field=True)
|
||||
path, field = fss.get(b"surfdataPath", return_field=True)
|
||||
|
||||
# This may match more than is used by Blender, but at least it shouldn't
|
||||
# miss any files.
|
||||
# The 'fluidsurface' prefix is defined in source/blender/makesdna/DNA_object_fluidsim_types.h
|
||||
bpath = bpathlib.BlendPath(path)
|
||||
yield result.BlockUsage(fss, bpath, path_full_field=field,
|
||||
is_sequence=True, block_name=block_name)
|
||||
yield result.BlockUsage(
|
||||
fss, bpath, path_full_field=field, is_sequence=True, block_name=block_name
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_Smokesim)
|
||||
def modifier_smoke_sim(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
my_log = log.getChild('modifier_smoke_sim')
|
||||
def modifier_smoke_sim(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
my_log = log.getChild("modifier_smoke_sim")
|
||||
|
||||
domain = modifier.get_pointer(b'domain')
|
||||
domain = modifier.get_pointer(b"domain")
|
||||
if domain is None:
|
||||
my_log.debug('Modifier %r (%r) has no domain',
|
||||
modifier[b'modifier', b'name'], block_name)
|
||||
my_log.debug(
|
||||
"Modifier %r (%r) has no domain", modifier[b"modifier", b"name"], block_name
|
||||
)
|
||||
return
|
||||
|
||||
pointcache = domain.get_pointer(b'point_cache')
|
||||
pointcache = domain.get_pointer(b"point_cache")
|
||||
if pointcache is None:
|
||||
return
|
||||
|
||||
format = domain.get(b'cache_file_format')
|
||||
format = domain.get(b"cache_file_format")
|
||||
extensions = {
|
||||
cdefs.PTCACHE_FILE_PTCACHE: cdefs.PTCACHE_EXT,
|
||||
cdefs.PTCACHE_FILE_OPENVDB: cdefs.PTCACHE_EXT_VDB
|
||||
cdefs.PTCACHE_FILE_OPENVDB: cdefs.PTCACHE_EXT_VDB,
|
||||
}
|
||||
yield from _walk_point_cache(ctx, block_name, modifier.bfile, pointcache, extensions[format])
|
||||
yield from _walk_point_cache(
|
||||
ctx, block_name, modifier.bfile, pointcache, extensions[format]
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_Fluid)
|
||||
def modifier_fluid(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
my_log = log.getChild("modifier_fluid")
|
||||
|
||||
domain = modifier.get_pointer(b"domain")
|
||||
if domain is None:
|
||||
my_log.debug(
|
||||
"Modifier %r (%r) has no domain", modifier[b"modifier", b"name"], block_name
|
||||
)
|
||||
return
|
||||
|
||||
# See fluid_bake_startjob() in physics_fluid.c
|
||||
path = domain[b"cache_directory"]
|
||||
path, field = domain.get(b"cache_directory", return_field=True)
|
||||
|
||||
log.info(" fluid cache at %s", path)
|
||||
bpath = bpathlib.BlendPath(path)
|
||||
yield result.BlockUsage(
|
||||
domain,
|
||||
bpath,
|
||||
path_full_field=field,
|
||||
is_sequence=True,
|
||||
block_name=block_name,
|
||||
)
|
||||
|
||||
|
||||
@mod_handler(cdefs.eModifierType_Cloth)
|
||||
def modifier_cloth(ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes) \
|
||||
-> typing.Iterator[result.BlockUsage]:
|
||||
pointcache = modifier.get_pointer(b'point_cache')
|
||||
def modifier_cloth(
|
||||
ctx: ModifierContext, modifier: blendfile.BlendFileBlock, block_name: bytes
|
||||
) -> typing.Iterator[result.BlockUsage]:
|
||||
pointcache = modifier.get_pointer(b"point_cache")
|
||||
if pointcache is None:
|
||||
return
|
||||
|
||||
yield from _walk_point_cache(ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT)
|
||||
yield from _walk_point_cache(
|
||||
ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT
|
||||
)
|
||||
|
||||
+52
-35
@@ -52,32 +52,37 @@ class BlockUsage:
|
||||
:ivar path_base_field: field containing the basename of this asset.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
block: blendfile.BlendFileBlock,
|
||||
asset_path: bpathlib.BlendPath,
|
||||
is_sequence: bool = False,
|
||||
path_full_field: dna.Field = None,
|
||||
path_dir_field: dna.Field = None,
|
||||
path_base_field: dna.Field = None,
|
||||
block_name: bytes = b'',
|
||||
) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
block: blendfile.BlendFileBlock,
|
||||
asset_path: bpathlib.BlendPath,
|
||||
is_sequence: bool = False,
|
||||
path_full_field: dna.Field = None,
|
||||
path_dir_field: dna.Field = None,
|
||||
path_base_field: dna.Field = None,
|
||||
block_name: bytes = b"",
|
||||
) -> None:
|
||||
if block_name:
|
||||
self.block_name = block_name
|
||||
else:
|
||||
self.block_name = self.guess_block_name(block)
|
||||
|
||||
assert isinstance(block, blendfile.BlendFileBlock)
|
||||
assert isinstance(asset_path, (bytes, bpathlib.BlendPath)), \
|
||||
'asset_path should be BlendPath, not %r' % type(asset_path)
|
||||
assert isinstance(
|
||||
asset_path, (bytes, bpathlib.BlendPath)
|
||||
), "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)
|
||||
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)
|
||||
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):
|
||||
asset_path = bpathlib.BlendPath(asset_path)
|
||||
@@ -95,26 +100,30 @@ class BlockUsage:
|
||||
@staticmethod
|
||||
def guess_block_name(block: blendfile.BlendFileBlock) -> bytes:
|
||||
try:
|
||||
return block[b'id', b'name']
|
||||
return block[b"id", b"name"]
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
return block[b'name']
|
||||
return block[b"name"]
|
||||
except KeyError:
|
||||
pass
|
||||
return b'-unnamed-'
|
||||
return b"-unnamed-"
|
||||
|
||||
def __repr__(self):
|
||||
if self.path_full_field is None:
|
||||
field_name = self.path_dir_field.name.name_full.decode() + \
|
||||
'/' + \
|
||||
self.path_base_field.name.name_full.decode()
|
||||
field_name = (
|
||||
self.path_dir_field.name.name_full.decode()
|
||||
+ "/"
|
||||
+ self.path_base_field.name.name_full.decode()
|
||||
)
|
||||
else:
|
||||
field_name = self.path_full_field.name.name_full.decode()
|
||||
return '<BlockUsage name=%r type=%r field=%r asset=%r%s>' % (
|
||||
self.block_name, self.block.dna_type_name,
|
||||
field_name, self.asset_path,
|
||||
' sequence' if self.is_sequence else ''
|
||||
return "<BlockUsage name=%r type=%r field=%r asset=%r%s>" % (
|
||||
self.block_name,
|
||||
self.block.dna_type_name,
|
||||
field_name,
|
||||
self.asset_path,
|
||||
" sequence" if self.is_sequence else "",
|
||||
)
|
||||
|
||||
def files(self) -> typing.Iterator[pathlib.Path]:
|
||||
@@ -130,7 +139,7 @@ class BlockUsage:
|
||||
path = self.__fspath__()
|
||||
if not self.is_sequence:
|
||||
if not path.exists():
|
||||
log.warning('Path %s does not exist for %s', path, self)
|
||||
log.warning("Path %s does not exist for %s", path, self)
|
||||
return
|
||||
yield path
|
||||
return
|
||||
@@ -138,14 +147,18 @@ class BlockUsage:
|
||||
try:
|
||||
yield from file_sequence.expand_sequence(path)
|
||||
except file_sequence.DoesNotExist:
|
||||
log.warning('Path %s does not exist for %s', path, self)
|
||||
log.warning("Path %s does not exist for %s", path, self)
|
||||
|
||||
def __fspath__(self) -> pathlib.Path:
|
||||
"""Determine the absolute path of the asset on the filesystem."""
|
||||
if self._abspath is None:
|
||||
bpath = self.block.bfile.abspath(self.asset_path)
|
||||
log.info('Resolved %s rel to %s -> %s',
|
||||
self.asset_path, self.block.bfile.filepath, bpath)
|
||||
log.info(
|
||||
"Resolved %s rel to %s -> %s",
|
||||
self.asset_path,
|
||||
self.block.bfile.filepath,
|
||||
bpath,
|
||||
)
|
||||
|
||||
as_path = pathlib.Path(bpath.to_path())
|
||||
|
||||
@@ -159,15 +172,19 @@ class BlockUsage:
|
||||
else:
|
||||
self._abspath = abs_parent / as_path.name
|
||||
|
||||
log.info('Resolving %s rel to %s -> %s',
|
||||
self.asset_path, self.block.bfile.filepath, self._abspath)
|
||||
log.info(
|
||||
"Resolving %s rel to %s -> %s",
|
||||
self.asset_path,
|
||||
self.block.bfile.filepath,
|
||||
self._abspath,
|
||||
)
|
||||
else:
|
||||
log.info('Reusing abspath %s', self._abspath)
|
||||
log.info("Reusing abspath %s", self._abspath)
|
||||
return self._abspath
|
||||
|
||||
abspath = property(__fspath__)
|
||||
|
||||
def __lt__(self, other: 'BlockUsage'):
|
||||
def __lt__(self, other: "BlockUsage"):
|
||||
"""Allow sorting for repeatable and predictable unit tests."""
|
||||
if not isinstance(other, BlockUsage):
|
||||
raise NotImplemented()
|
||||
|
||||
Reference in New Issue
Block a user