# ***** 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 """Low-level functions called by file2block. Those can expand data blocks and yield their dependencies (e.g. other data blocks necessary to render/display/work with the given data block). """ import logging import typing 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'} _funcs_for_code = {} # type: typing.Dict[bytes, typing.Callable] log = logging.getLogger(__name__) 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()) _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)) def dna_code(block_code: str): """Decorator, marks decorated func as expander for that DNA code.""" assert isinstance(block_code, str) def decorator(wrapped): _funcs_for_code[block_code.encode()] = wrapped return wrapped return decorator def _expand_generic_material(block: blendfile.BlendFileBlock): 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'): # 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') def _expand_generic_nodetree(block: blendfile.BlendFileBlock): assert block.dna_type.dna_type_id == b'bNodeTree' nodes = block.get_pointer((b'nodes', b'first')) for node in iterators.listbase(nodes): 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') def _expand_generic_nodetree_id(block: blendfile.BlendFileBlock): 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') if block_adt: yield block_adt.get_pointer(b'action') # TODO, NLA @dna_code('AR') def _expand_armature(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) @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'): yield block.get_pointer(fieldname) @dna_code('GR') def _expand_group(block: blendfile.BlendFileBlock): log.debug('Collection/group Block: %s (name=%s)', block, block.id_name) objects = block.get_pointer((b'gobject', b'first')) for item in iterators.listbase(objects): yield item.get_pointer(b'ob') # Recurse through child collections. try: 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') if subcoll is None: continue 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 # converting files from Blender 2.79 to 2.80. This error # isn't reproducible with just Blender 2.80. yield subcoll continue 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') 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') 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') except KeyError: # Groups were removed from Blender 2.8 pass @dna_code('MB') def _expand_metaball(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_material(block) @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') # TODO, TexFace? - it will be slow, we could simply ignore :S @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') def _expand_object(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_material(block) yield block.get_pointer(b'data') 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') # 'ob->pose->chanbase[...].custom' block_pose = block.get_pointer(b'pose') if block_pose: 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')) for pose_chan in iterators.listbase(channels): 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')) for psystem in iterators.listbase(psystems): 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'] # 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') @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'] if block_ren_as == cdefs.PART_DRAW_GR: yield block.get_pointer(b'dup_group') elif block_ren_as == cdefs.PART_DRAW_OB: yield block.get_pointer(b'dup_ob') @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) # 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')) for base in iterators.listbase(bases): yield base.get_pointer(b'object') # Sequence Editor 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', } for strip, strip_type in iterators.sequencer_strips(block_ed): try: field_name = strip_type_to_field[strip_type] except KeyError: continue yield strip.get_pointer(field_name) @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') @dna_code('WO') def _expand_world(block: blendfile.BlendFileBlock): yield from _expand_generic_animdata(block) yield from _expand_generic_nodetree_id(block) yield from _expand_generic_mtex(block)