216 lines
7.2 KiB
Python
Executable File
216 lines
7.2 KiB
Python
Executable File
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
#
|
|
# ***** END GPL LICENCE BLOCK *****
|
|
#
|
|
# (c) 2014, Blender Foundation - Campbell Barton
|
|
# (c) 2018, Blender Foundation - Sybren A. Stüvel
|
|
"""Block walkers.
|
|
|
|
From a Blend file data block, iter_assts() yields all the referred-to assets.
|
|
"""
|
|
|
|
import functools
|
|
import logging
|
|
import typing
|
|
|
|
from blender_asset_tracer import blendfile, bpathlib, cdefs
|
|
from blender_asset_tracer.blendfile import iterators
|
|
from . import result, modifier_walkers
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_warned_about_types = set() # type: typing.Set[bytes]
|
|
_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"
|
|
|
|
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())
|
|
_warned_about_types.add(block.code)
|
|
return
|
|
|
|
log.debug("Tracing block %r", block)
|
|
yield from block_reader(block)
|
|
|
|
|
|
def dna_code(block_code: str):
|
|
"""Decorator, marks decorated func as handler for that DNA code."""
|
|
|
|
assert isinstance(block_code, str)
|
|
|
|
def decorator(wrapped):
|
|
_funcs_for_code[block_code.encode()] = wrapped
|
|
return wrapped
|
|
|
|
return decorator
|
|
|
|
|
|
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):
|
|
log.debug("Datablock %r is packed; skipping", block.id_name)
|
|
return
|
|
|
|
yield from wrapped(block, *args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
|
|
@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)
|
|
yield result.BlockUsage(block, path, path_full_field=field)
|
|
|
|
|
|
@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)
|
|
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 in {cdefs.IMA_SRC_SEQUENCE, cdefs.IMA_SRC_TILED}
|
|
|
|
yield result.BlockUsage(block, pathname, is_sequence, path_full_field=field)
|
|
|
|
|
|
@dna_code("LI")
|
|
def library(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
|
|
"""Library data blocks."""
|
|
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
|
|
# absolute path of the file by Blender (see BKE_library_filepath_set). This
|
|
# is thus not a property we have to report or rewrite.
|
|
|
|
|
|
@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)
|
|
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)
|
|
|
|
|
|
@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)
|
|
# 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")
|
|
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)
|
|
|
|
try:
|
|
mod_handler = modifier_walkers.modifier_handlers[mod_type]
|
|
except KeyError:
|
|
continue
|
|
yield from mod_handler(ctx, block_mod, block_name)
|
|
|
|
|
|
@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")
|
|
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})
|
|
|
|
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")
|
|
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_strip,
|
|
asset_path,
|
|
is_sequence=is_sequence,
|
|
path_dir_field=dn_field,
|
|
path_base_field=bn_field,
|
|
)
|
|
|
|
|
|
@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)
|
|
yield result.BlockUsage(block, path, path_full_field=field)
|
|
|
|
|
|
@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
|
|
return
|
|
yield result.BlockUsage(block, path, path_full_field=field)
|