Ported BlendFileBlock.get_recursive_iter()
Also simplified get_file_offset(), because its optional parameters are never used anyway.
This commit is contained in:
parent
87300df6a3
commit
7165d121bd
@ -326,41 +326,24 @@ class BlendFileBlock:
|
|||||||
assert (type(dna_type_id) is bytes)
|
assert (type(dna_type_id) is bytes)
|
||||||
self.refine_type_from_index(self.bfile.sdna_index_from_id[dna_type_id])
|
self.refine_type_from_index(self.bfile.sdna_index_from_id[dna_type_id])
|
||||||
|
|
||||||
def get_file_offset(self, path,
|
def get_file_offset(self, path: bytes) -> (int, int):
|
||||||
default=...,
|
"""Return (offset, length)"""
|
||||||
sdna_index_refine=None,
|
|
||||||
base_index=0,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Return (offset, length)
|
|
||||||
"""
|
|
||||||
assert isinstance(path, bytes)
|
assert isinstance(path, bytes)
|
||||||
|
|
||||||
|
# TODO: refactor to just return the length, and check whether this isn't actually
|
||||||
|
# simply the same as self.size.
|
||||||
ofs = self.file_offset
|
ofs = self.file_offset
|
||||||
if base_index != 0:
|
field, _ = self.dna_type.field_from_path(self.bfile.header.pointer_size, path)
|
||||||
assert (base_index < self.count)
|
return ofs, field.name.array_size
|
||||||
ofs += (self.size // self.count) * base_index
|
|
||||||
self.bfile.fileobj.seek(ofs, os.SEEK_SET)
|
|
||||||
|
|
||||||
if sdna_index_refine is None:
|
|
||||||
sdna_index_refine = self.sdna_index
|
|
||||||
else:
|
|
||||||
self.bfile.ensure_subtype_smaller(self.sdna_index, sdna_index_refine)
|
|
||||||
|
|
||||||
dna_struct = self.bfile.structs[sdna_index_refine]
|
|
||||||
field = dna_struct.field_from_path(
|
|
||||||
self.bfile.header, self.bfile.fileobj, path)
|
|
||||||
|
|
||||||
return self.bfile.fileobj.tell(), field.dna_name.array_size
|
|
||||||
|
|
||||||
def get(self,
|
def get(self,
|
||||||
path: dna.FieldPath,
|
path: dna.FieldPath,
|
||||||
default=...,
|
default=...,
|
||||||
sdna_index_refine=None,
|
sdna_index_refine=None,
|
||||||
null_terminated: typing.Optional[bool]=None,
|
null_terminated: typing.Optional[bool] = None,
|
||||||
as_str=True,
|
as_str=True,
|
||||||
base_index=0,
|
base_index=0,
|
||||||
):
|
) -> typing.Any:
|
||||||
"""Read a property and return the value.
|
"""Read a property and return the value.
|
||||||
|
|
||||||
:param path: name of the property (like `b'loc'`), tuple of names
|
:param path: name of the property (like `b'loc'`), tuple of names
|
||||||
@ -395,12 +378,20 @@ class BlendFileBlock:
|
|||||||
null_terminated=null_terminated, as_str=as_str,
|
null_terminated=null_terminated, as_str=as_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_recursive_iter(self, path, path_root=b"",
|
def get_recursive_iter(self,
|
||||||
|
path: dna.FieldPath,
|
||||||
|
path_root: dna.FieldPath = b'',
|
||||||
default=...,
|
default=...,
|
||||||
sdna_index_refine=None,
|
sdna_index_refine=None,
|
||||||
use_nil=True, use_str=True,
|
null_terminated: typing.Optional[bool] = None,
|
||||||
|
as_str=True,
|
||||||
base_index=0,
|
base_index=0,
|
||||||
):
|
) -> typing.Iterator[typing.Tuple[bytes, typing.Any]]:
|
||||||
|
"""Generator, yields (path, property value) tuples.
|
||||||
|
|
||||||
|
If a property cannot be decoded, a string representing its DNA type
|
||||||
|
name is used as its value instead, between pointy brackets.
|
||||||
|
"""
|
||||||
if path_root:
|
if path_root:
|
||||||
path_full = (
|
path_full = (
|
||||||
(path_root if type(path_root) is tuple else (path_root,)) +
|
(path_root if type(path_root) is tuple else (path_root,)) +
|
||||||
@ -409,22 +400,26 @@ class BlendFileBlock:
|
|||||||
path_full = path
|
path_full = path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Try accessing as simple property
|
||||||
yield (path_full,
|
yield (path_full,
|
||||||
self.get(path_full, default, sdna_index_refine, use_nil, use_str, base_index))
|
self.get(path_full, default, sdna_index_refine, null_terminated, as_str,
|
||||||
except NotImplementedError as ex:
|
base_index))
|
||||||
msg, dna_name, dna_type = ex.args
|
except exceptions.NoReaderImplemented as ex:
|
||||||
struct_index = self.bfile.sdna_index_from_id.get(dna_type.dna_type_id, None)
|
# This was not a simple property, so recurse into its DNA Struct.
|
||||||
|
dna_type = ex.dna_type
|
||||||
|
struct_index = self.bfile.sdna_index_from_id.get(dna_type.dna_type_id)
|
||||||
if struct_index is None:
|
if struct_index is None:
|
||||||
yield (path_full, "<%s>" % dna_type.dna_type_id.decode('ascii'))
|
yield (path_full, "<%s>" % dna_type.dna_type_id.decode('ascii'))
|
||||||
else:
|
return
|
||||||
struct = self.bfile.structs[struct_index]
|
|
||||||
for f in struct.fields:
|
# Recurse through the fields.
|
||||||
yield from self.get_recursive_iter(
|
for f in dna_type.fields:
|
||||||
f.dna_name.name_only, path_full, default, None, use_nil, use_str, 0)
|
yield from self.get_recursive_iter(f.name.name_only, path_full, default=default,
|
||||||
|
null_terminated=null_terminated, as_str=as_str)
|
||||||
|
|
||||||
def items_recursive_iter(self):
|
def items_recursive_iter(self):
|
||||||
for k in self.keys():
|
for k in self.keys():
|
||||||
yield from self.get_recursive_iter(k, use_str=False)
|
yield from self.get_recursive_iter(k, as_str=False)
|
||||||
|
|
||||||
def get_data_hash(self):
|
def get_data_hash(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import typing
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from . import dna_io, header
|
from . import dna_io, header, exceptions
|
||||||
|
|
||||||
# Either a simple path b'propname', or a tuple (b'parentprop', b'actualprop', arrayindex)
|
# Either a simple path b'propname', or a tuple (b'parentprop', b'actualprop', arrayindex)
|
||||||
FieldPath = typing.Union[bytes, typing.Iterable[typing.Union[bytes, int]]]
|
FieldPath = typing.Union[bytes, typing.Iterable[typing.Union[bytes, int]]]
|
||||||
@ -115,6 +115,14 @@ class Struct:
|
|||||||
self._fields.append(field)
|
self._fields.append(field)
|
||||||
self._fields_by_name[field.name.name_only] = field
|
self._fields_by_name[field.name.name_only] = field
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fields(self) -> typing.List[Field]:
|
||||||
|
"""Return the fields of this Struct.
|
||||||
|
|
||||||
|
Do not modify the returned list; use append_field() instead.
|
||||||
|
"""
|
||||||
|
return self._fields
|
||||||
|
|
||||||
def field_from_path(self,
|
def field_from_path(self,
|
||||||
pointer_size: int,
|
pointer_size: int,
|
||||||
path: FieldPath) \
|
path: FieldPath) \
|
||||||
@ -231,8 +239,9 @@ class Struct:
|
|||||||
try:
|
try:
|
||||||
simple_reader = simple_readers[dna_type.dna_type_id]
|
simple_reader = simple_readers[dna_type.dna_type_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NotImplementedError("%r exists but isn't pointer, can't resolve field %r" %
|
raise exceptions.NoReaderImplemented(
|
||||||
(path, dna_name.name_only), dna_name, dna_type)
|
"%r exists but isn't pointer, can't resolve field %r" % (path, dna_name.name_only),
|
||||||
|
dna_name, dna_type) from None
|
||||||
|
|
||||||
if isinstance(path, tuple) and len(path) > 1 and isinstance(path[-1], int):
|
if isinstance(path, tuple) and len(path) > 1 and isinstance(path[-1], int):
|
||||||
# The caller wants to get a single item from an array. The offset we seeked to already
|
# The caller wants to get a single item from an array. The offset we seeked to already
|
||||||
|
|||||||
@ -37,3 +37,18 @@ class BlendFileError(Exception):
|
|||||||
|
|
||||||
class NoDNA1Block(BlendFileError):
|
class NoDNA1Block(BlendFileError):
|
||||||
"""Raised when the blend file contains no DNA1 block."""
|
"""Raised when the blend file contains no DNA1 block."""
|
||||||
|
|
||||||
|
|
||||||
|
class NoReaderImplemented(NotImplementedError):
|
||||||
|
"""Raised when reading a property of a non-implemented type.
|
||||||
|
|
||||||
|
This indicates that the property should be read using some dna.Struct.
|
||||||
|
|
||||||
|
:type dna_name: blender_asset_tracer.blendfile.dna.Name
|
||||||
|
:type dna_type: blender_asset_tracer.blendfile.dna.Struct
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message: str, dna_name, dna_type):
|
||||||
|
super().__init__(message)
|
||||||
|
self.dna_name = dna_name
|
||||||
|
self.dna_type = dna_type
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import os
|
|||||||
from blender_asset_tracer import blendfile
|
from blender_asset_tracer import blendfile
|
||||||
|
|
||||||
|
|
||||||
class BlendLoadingTest(unittest.TestCase):
|
class BlendFileBlockTest(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.blendfiles = pathlib.Path(__file__).with_name('blendfiles')
|
cls.blendfiles = pathlib.Path(__file__).with_name('blendfiles')
|
||||||
@ -18,10 +18,12 @@ class BlendLoadingTest(unittest.TestCase):
|
|||||||
if self.bf:
|
if self.bf:
|
||||||
self.bf.close()
|
self.bf.close()
|
||||||
|
|
||||||
def test_some_properties(self):
|
def test_loading(self):
|
||||||
self.bf = blendfile.BlendFile(self.blendfiles / 'basic_file.blend')
|
self.bf = blendfile.BlendFile(self.blendfiles / 'basic_file.blend')
|
||||||
self.assertFalse(self.bf.is_compressed)
|
self.assertFalse(self.bf.is_compressed)
|
||||||
self.assertEqual(1, len(self.bf.code_index[b'OB']))
|
|
||||||
|
def test_some_properties(self):
|
||||||
|
self.bf = blendfile.BlendFile(self.blendfiles / 'basic_file.blend')
|
||||||
ob = self.bf.code_index[b'OB'][0]
|
ob = self.bf.code_index[b'OB'][0]
|
||||||
self.assertEqual('Object', ob.dna_type_name)
|
self.assertEqual('Object', ob.dna_type_name)
|
||||||
|
|
||||||
@ -51,3 +53,29 @@ class BlendLoadingTest(unittest.TestCase):
|
|||||||
mesh = self.bf.block_from_addr[mesh_ptr]
|
mesh = self.bf.block_from_addr[mesh_ptr]
|
||||||
mname = mesh.get((b'id', b'name'))
|
mname = mesh.get((b'id', b'name'))
|
||||||
self.assertEqual('MECube³', mname)
|
self.assertEqual('MECube³', mname)
|
||||||
|
|
||||||
|
def test_get_recursive_iter(self):
|
||||||
|
self.bf = blendfile.BlendFile(self.blendfiles / 'basic_file.blend')
|
||||||
|
ob = self.bf.code_index[b'OB'][0]
|
||||||
|
assert isinstance(ob, blendfile.BlendFileBlock)
|
||||||
|
|
||||||
|
# No recursing, just an array property.
|
||||||
|
gen = ob.get_recursive_iter(b'loc')
|
||||||
|
self.assertEqual([(b'loc', [2.0, 3.0, 5.0])], list(gen))
|
||||||
|
|
||||||
|
# Recurse into an object
|
||||||
|
gen = ob.get_recursive_iter(b'id')
|
||||||
|
self.assertEqual(
|
||||||
|
[((b'id', b'next'), 0),
|
||||||
|
((b'id', b'prev'), 0),
|
||||||
|
((b'id', b'newid'), 0),
|
||||||
|
((b'id', b'lib'), 0),
|
||||||
|
((b'id', b'name'), 'OBümlaut'),
|
||||||
|
((b'id', b'flag'), 0),
|
||||||
|
((b'id', b'tag'), 1024),
|
||||||
|
((b'id', b'us'), 1),
|
||||||
|
((b'id', b'icon_id'), 0),
|
||||||
|
((b'id', b'recalc'), 0),
|
||||||
|
((b'id', b'pad'), 0),
|
||||||
|
],
|
||||||
|
list(gen)[:-2]) # the last 2 properties are pointers and change when saving.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user