320 lines
9.8 KiB
Python
Executable File
320 lines
9.8 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) 2018, Blender Foundation - Sybren A. Stüvel
|
|
"""Modifier handling code used in blocks2assets.py
|
|
|
|
The modifier_xxx() functions all yield result.BlockUsage objects for external
|
|
files used by the modifiers.
|
|
"""
|
|
import logging
|
|
import typing
|
|
|
|
from blender_asset_tracer import blendfile, bpathlib, cdefs
|
|
from . import result
|
|
|
|
log = logging.getLogger(__name__)
|
|
modifier_handlers = {} # type: typing.Dict[int, typing.Callable]
|
|
|
|
|
|
class ModifierContext:
|
|
"""Meta-info for modifier expansion.
|
|
|
|
Currently just contains the object on which the modifier is defined.
|
|
"""
|
|
|
|
def __init__(self, owner: blendfile.BlendFileBlock) -> None:
|
|
assert owner.dna_type_name == "Object"
|
|
self.owner = owner
|
|
|
|
|
|
def mod_handler(dna_num: int):
|
|
"""Decorator, marks decorated func as handler for that modifier number."""
|
|
|
|
assert isinstance(dna_num, int)
|
|
|
|
def decorator(wrapped):
|
|
modifier_handlers[dna_num] = wrapped
|
|
return wrapped
|
|
|
|
return decorator
|
|
|
|
|
|
@mod_handler(cdefs.eModifierType_MeshCache)
|
|
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
|
|
)
|
|
|
|
|
|
@mod_handler(cdefs.eModifierType_MeshSequenceCache)
|
|
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")
|
|
if cache_file is None:
|
|
return
|
|
|
|
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,
|
|
)
|
|
|
|
|
|
@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"]:
|
|
return
|
|
|
|
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
|
|
)
|
|
|
|
|
|
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.
|
|
"""
|
|
if dblock is None:
|
|
return
|
|
|
|
tx = dblock.get_pointer(prop_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]:
|
|
"""Yield block usages from an image propery.
|
|
|
|
Assumes dblock[prop_name] is an image data block.
|
|
"""
|
|
if not dblock:
|
|
return
|
|
|
|
try:
|
|
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)
|
|
return
|
|
|
|
if not ima:
|
|
return
|
|
|
|
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)
|
|
|
|
|
|
@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)
|
|
|
|
|
|
@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 _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)
|
|
bpath = bpathlib.BlendPath(path)
|
|
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)
|
|
if not name:
|
|
# See ptcache_filename() in pointcache.c
|
|
idname = ctx.owner[b"id", b"name"]
|
|
name = idname[2:].hex().upper().encode()
|
|
path = b"//%b%b/%b_*%b" % (
|
|
cdefs.PTCACHE_PATH,
|
|
bfile.filepath.stem.encode(),
|
|
name,
|
|
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,
|
|
)
|
|
|
|
|
|
@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")
|
|
if psys is None:
|
|
return
|
|
|
|
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
|
|
)
|
|
|
|
|
|
@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")
|
|
|
|
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
|
|
)
|
|
return
|
|
|
|
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
|
|
)
|
|
|
|
|
|
@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")
|
|
|
|
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
|
|
|
|
pointcache = domain.get_pointer(b"point_cache")
|
|
if pointcache is None:
|
|
return
|
|
|
|
format = domain.get(b"cache_file_format")
|
|
extensions = {
|
|
cdefs.PTCACHE_FILE_PTCACHE: cdefs.PTCACHE_EXT,
|
|
cdefs.PTCACHE_FILE_OPENVDB: cdefs.PTCACHE_EXT_VDB,
|
|
}
|
|
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")
|
|
if pointcache is None:
|
|
return
|
|
|
|
yield from _walk_point_cache(
|
|
ctx, block_name, modifier.bfile, pointcache, cdefs.PTCACHE_EXT
|
|
)
|