Added BlendFileBlock.id_name property

Evaluated only once, so safe to call multiple times without producing
excessive disk I/O. Returns None instead of raising KeyError when there is
no (b'id', b'name') field.
This commit is contained in:
Sybren A. Stüvel 2018-03-02 11:26:02 +01:00
parent 2bb9cffa49
commit 7167d51730
4 changed files with 26 additions and 11 deletions

View File

@ -326,6 +326,7 @@ class BlendFileBlock:
Points to the data after the block header. Points to the data after the block header.
""" """
self.endian = bfile.header.endian self.endian = bfile.header.endian
self._id_name = ... # see the id_name property
header_struct = bfile.block_header_struct header_struct = bfile.block_header_struct
data = bfile.fileobj.read(header_struct.size) data = bfile.fileobj.read(header_struct.size)
@ -382,6 +383,20 @@ class BlendFileBlock:
def dna_type_name(self) -> str: def dna_type_name(self) -> str:
return self.dna_type.dna_type_id.decode('ascii') return self.dna_type.dna_type_id.decode('ascii')
@property
def id_name(self) -> typing.Optional[bytes]:
"""Same as block[b'id', b'name']; None if there is no such field.
Evaluated only once, so safe to call multiple times without producing
excessive disk I/O.
"""
if self._id_name is ...:
try:
self._id_name = self[b'id', b'name']
except KeyError:
self._id_name = None
return self._id_name
def refine_type_from_index(self, sdna_index: int): def refine_type_from_index(self, sdna_index: int):
"""Change the DNA Struct associated with this block. """Change the DNA Struct associated with this block.
@ -543,7 +558,8 @@ class BlendFileBlock:
-> typing.Iterator['BlendFileBlock']: -> typing.Iterator['BlendFileBlock']:
"""Dereference pointers from an array-of-pointers field. """Dereference pointers from an array-of-pointers field.
Use this function when you have a field like materials: `Mat **mat` Use this function when you have a field like Mesh materials:
`Mat **mat`
:param path: The array-of-pointers field. :param path: The array-of-pointers field.
:param array_size: Number of items in the array. If None, the :param array_size: Number of items in the array. If None, the
@ -571,7 +587,8 @@ class BlendFileBlock:
-> typing.Iterator['BlendFileBlock']: -> typing.Iterator['BlendFileBlock']:
"""Yield blocks from a fixed-size array field. """Yield blocks from a fixed-size array field.
Use this function when you have a field like lamp textures: `MTex *mtex[18]` Use this function when you have a field like lamp textures:
`MTex *mtex[18]`
The size of the array is determined automatically by the size in bytes The size of the array is determined automatically by the size in bytes
of the field divided by the pointer size of the blend file. of the field divided by the pointer size of the blend file.

View File

@ -5,7 +5,6 @@ From a Blend file data block, iter_assts() yields all the referred-to assets.
import functools import functools
import logging import logging
import sys
import typing import typing
from blender_asset_tracer import blendfile, bpathlib from blender_asset_tracer import blendfile, bpathlib
@ -117,10 +116,9 @@ def movie_clip(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockU
def object_block(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]: def object_block(block: blendfile.BlendFileBlock) -> typing.Iterator[result.BlockUsage]:
"""Object data blocks.""" """Object data blocks."""
# 'ob->modifiers[...].filepath' # 'ob->modifiers[...].filepath'
ob_idname = block[b'id', b'name']
mods = block.get_pointer((b'modifiers', b'first')) mods = block.get_pointer((b'modifiers', b'first'))
for mod_idx, block_mod in enumerate(iterators.listbase(mods, next_path=(b'modifier', b'next'))): 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) block_name = b'%s.modifiers[%d]' % (block.id_name, mod_idx)
mod_type = block_mod[b'modifier', b'type'] mod_type = block_mod[b'modifier', b'type']
log.debug('Tracing modifier %s, type=%d', block_name.decode(), mod_type) log.debug('Tracing modifier %s, type=%d', block_name.decode(), mod_type)

View File

@ -139,7 +139,7 @@ class BlendFileBlockTest(AbstractBlendFileTest):
ob = self.bf.code_index[b'OB'][0] ob = self.bf.code_index[b'OB'][0]
assert isinstance(ob, blendfile.BlendFileBlock) assert isinstance(ob, blendfile.BlendFileBlock)
self.assertEqual('OBümlaut', ob[b'id', b'name'].decode()) self.assertEqual('OBümlaut', ob.id_name.decode())
class PointerTest(AbstractBlendFileTest): class PointerTest(AbstractBlendFileTest):
@ -150,7 +150,7 @@ class PointerTest(AbstractBlendFileTest):
scenes = self.bf.code_index[b'SC'] scenes = self.bf.code_index[b'SC']
self.assertEqual(1, len(scenes), 'expecting 1 scene') self.assertEqual(1, len(scenes), 'expecting 1 scene')
scene = scenes[0] scene = scenes[0]
self.assertEqual(b'SCScene', scene[b'id', b'name']) self.assertEqual(b'SCScene', scene.id_name)
ed_ptr = scene[b'ed'] ed_ptr = scene[b'ed']
self.assertEqual(140051431100936, ed_ptr) self.assertEqual(140051431100936, ed_ptr)
@ -232,7 +232,7 @@ class ArrayTest(AbstractBlendFileTest):
name = b'MAMaterial.001' name = b'MAMaterial.001'
else: else:
name = b'MAMaterial.002' name = b'MAMaterial.002'
self.assertEqual(name, material[b'id', b'name']) self.assertEqual(name, material.id_name)
def test_array_of_lamp_textures(self): def test_array_of_lamp_textures(self):
self.bf = blendfile.BlendFile(self.blendfiles / 'lamp_textures.blend') self.bf = blendfile.BlendFile(self.blendfiles / 'lamp_textures.blend')
@ -242,7 +242,7 @@ class ArrayTest(AbstractBlendFileTest):
mtex0 = lamp.get_pointer(b'mtex') mtex0 = lamp.get_pointer(b'mtex')
tex = mtex0.get_pointer(b'tex') tex = mtex0.get_pointer(b'tex')
self.assertEqual(b'TE', tex.code) self.assertEqual(b'TE', tex.code)
self.assertEqual(b'TEClouds', tex[b'id', b'name']) self.assertEqual(b'TEClouds', tex.id_name)
for i, mtex in enumerate(lamp.iter_fixed_array_of_pointers(b'mtex')): for i, mtex in enumerate(lamp.iter_fixed_array_of_pointers(b'mtex')):
if i == 0: if i == 0:
@ -254,7 +254,7 @@ class ArrayTest(AbstractBlendFileTest):
tex = mtex.get_pointer(b'tex') tex = mtex.get_pointer(b'tex')
self.assertEqual(b'TE', tex.code) self.assertEqual(b'TE', tex.code)
self.assertEqual(name, tex[b'id', b'name']) self.assertEqual(name, tex.id_name)
class LoadCompressedTest(AbstractBlendFileTest): class LoadCompressedTest(AbstractBlendFileTest):

View File

@ -43,7 +43,7 @@ class AssetHoldingBlocksTest(AbstractTracerTest):
# Do some arbitrary tests that convince us stuff is read well. # Do some arbitrary tests that convince us stuff is read well.
if block.code == b'SC': if block.code == b'SC':
seen_scene = True seen_scene = True
self.assertEqual(b'SCScene', block[b'id', b'name']) self.assertEqual(b'SCScene', block.id_name)
continue continue
if block.code == b'OB': if block.code == b'OB':