Ported iteration over properties
This commit is contained in:
parent
6175f6c592
commit
63c7e4fcef
@ -340,7 +340,7 @@ class BlendFileBlock:
|
|||||||
path: dna.FieldPath,
|
path: dna.FieldPath,
|
||||||
default=...,
|
default=...,
|
||||||
sdna_index_refine=None,
|
sdna_index_refine=None,
|
||||||
null_terminated: typing.Optional[bool] = None,
|
null_terminated=True,
|
||||||
as_str=True,
|
as_str=True,
|
||||||
base_index=0,
|
base_index=0,
|
||||||
) -> typing.Any:
|
) -> typing.Any:
|
||||||
@ -353,8 +353,8 @@ class BlendFileBlock:
|
|||||||
:param default: The value to return when the field does not exist.
|
:param default: The value to return when the field does not exist.
|
||||||
Use Ellipsis (the default value) to raise a KeyError instead.
|
Use Ellipsis (the default value) to raise a KeyError instead.
|
||||||
:param null_terminated: Only used when reading bytes or strings. When
|
:param null_terminated: Only used when reading bytes or strings. When
|
||||||
True, stops reading at the first zero byte. Defaults to the same
|
True, stops reading at the first zero byte; be careful with this
|
||||||
value as `as_str`, as this is mostly used for string data.
|
when reading binary data.
|
||||||
:param as_str: When True, automatically decode bytes to string
|
:param as_str: When True, automatically decode bytes to string
|
||||||
(assumes UTF-8 encoding).
|
(assumes UTF-8 encoding).
|
||||||
"""
|
"""
|
||||||
@ -383,7 +383,7 @@ class BlendFileBlock:
|
|||||||
path_root: dna.FieldPath = b'',
|
path_root: dna.FieldPath = b'',
|
||||||
default=...,
|
default=...,
|
||||||
sdna_index_refine=None,
|
sdna_index_refine=None,
|
||||||
null_terminated: typing.Optional[bool] = None,
|
null_terminated=True,
|
||||||
as_str=True,
|
as_str=True,
|
||||||
base_index=0,
|
base_index=0,
|
||||||
) -> typing.Iterator[typing.Tuple[bytes, typing.Any]]:
|
) -> typing.Iterator[typing.Tuple[bytes, typing.Any]]:
|
||||||
@ -417,10 +417,6 @@ class BlendFileBlock:
|
|||||||
yield from self.get_recursive_iter(f.name.name_only, path_full, default=default,
|
yield from self.get_recursive_iter(f.name.name_only, path_full, default=default,
|
||||||
null_terminated=null_terminated, as_str=as_str)
|
null_terminated=null_terminated, as_str=as_str)
|
||||||
|
|
||||||
def items_recursive_iter(self):
|
|
||||||
for k in self.keys():
|
|
||||||
yield from self.get_recursive_iter(k, as_str=False)
|
|
||||||
|
|
||||||
def get_data_hash(self):
|
def get_data_hash(self):
|
||||||
"""
|
"""
|
||||||
Generates a 'hash' that can be used instead of addr_old as block id, and that should be 'stable' across .blend
|
Generates a 'hash' that can be used instead of addr_old as block id, and that should be 'stable' across .blend
|
||||||
@ -434,7 +430,7 @@ class BlendFileBlock:
|
|||||||
self.file.header, self.file.handle, k).dna_name.is_pointer
|
self.file.header, self.file.handle, k).dna_name.is_pointer
|
||||||
|
|
||||||
hsh = 1
|
hsh = 1
|
||||||
for k, v in self.items_recursive_iter():
|
for k, v in self.items_recursive():
|
||||||
if not _is_pointer(self, k):
|
if not _is_pointer(self, k):
|
||||||
hsh = zlib.adler32(str(v).encode(), hsh)
|
hsh = zlib.adler32(str(v).encode(), hsh)
|
||||||
return hsh
|
return hsh
|
||||||
@ -486,27 +482,31 @@ class BlendFileBlock:
|
|||||||
# Python convenience API
|
# Python convenience API
|
||||||
|
|
||||||
# dict like access
|
# dict like access
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, path: dna.FieldPath):
|
||||||
return self.get(item, as_str=False)
|
return self.get(path, as_str=False)
|
||||||
|
|
||||||
def __setitem__(self, item, value):
|
def __setitem__(self, item, value):
|
||||||
self.set(item, value)
|
self.set(item, value)
|
||||||
|
|
||||||
def keys(self):
|
def keys(self) -> typing.Iterator[bytes]:
|
||||||
return (f.dna_name.name_only for f in self.dna_type.fields)
|
"""Generator, yields all field names of this block."""
|
||||||
|
return (f.name.name_only for f in self.dna_type.fields)
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
for k in self.keys():
|
for k in self.keys():
|
||||||
try:
|
try:
|
||||||
yield self[k]
|
yield self[k]
|
||||||
except NotImplementedError as ex:
|
except exceptions.NoReaderImplemented as ex:
|
||||||
msg, dna_name, dna_type = ex.args
|
yield '<%s>' % ex.dna_type.dna_type_id.decode('ascii')
|
||||||
yield "<%s>" % dna_type.dna_type_id.decode('ascii')
|
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
for k in self.keys():
|
for k in self.keys():
|
||||||
try:
|
try:
|
||||||
yield (k, self[k])
|
yield (k, self[k])
|
||||||
except NotImplementedError as ex:
|
except exceptions.NoReaderImplemented as ex:
|
||||||
msg, dna_name, dna_type = ex.args
|
yield (k, '<%s>' % ex.dna_type.dna_type_id.decode('ascii'))
|
||||||
yield (k, "<%s>" % dna_type.dna_type_id.decode('ascii'))
|
|
||||||
|
def items_recursive(self):
|
||||||
|
"""Generator, yields (property path, property value) recursively for all properties."""
|
||||||
|
for k in self.keys():
|
||||||
|
yield from self.get_recursive_iter(k, as_str=False)
|
||||||
|
|||||||
@ -181,7 +181,7 @@ class Struct:
|
|||||||
fileobj: typing.BinaryIO,
|
fileobj: typing.BinaryIO,
|
||||||
path: FieldPath,
|
path: FieldPath,
|
||||||
default=...,
|
default=...,
|
||||||
null_terminated: typing.Optional[bool]=None,
|
null_terminated=True,
|
||||||
as_str=True,
|
as_str=True,
|
||||||
):
|
):
|
||||||
"""Read the value of the field from the blend file.
|
"""Read the value of the field from the blend file.
|
||||||
@ -196,8 +196,8 @@ class Struct:
|
|||||||
:param default: The value to return when the field does not exist.
|
:param default: The value to return when the field does not exist.
|
||||||
Use Ellipsis (the default value) to raise a KeyError instead.
|
Use Ellipsis (the default value) to raise a KeyError instead.
|
||||||
:param null_terminated: Only used when reading bytes or strings. When
|
:param null_terminated: Only used when reading bytes or strings. When
|
||||||
True, stops reading at the first zero byte. Defaults to the same
|
True, stops reading at the first zero byte. Be careful with this
|
||||||
value as `as_str`, as this is mostly used for string data.
|
default when reading binary data.
|
||||||
:param as_str: When True, automatically decode bytes to string
|
:param as_str: When True, automatically decode bytes to string
|
||||||
(assumes UTF-8 encoding).
|
(assumes UTF-8 encoding).
|
||||||
"""
|
"""
|
||||||
|
|||||||
Binary file not shown.
@ -208,8 +208,7 @@ class StructTest(unittest.TestCase):
|
|||||||
fileobj = mock.MagicMock(io.BufferedReader)
|
fileobj = mock.MagicMock(io.BufferedReader)
|
||||||
fileobj.read.return_value = b'\x01\x02\x03\x04\xff\xfe\xfd\xfa\x00dummydata'
|
fileobj.read.return_value = b'\x01\x02\x03\x04\xff\xfe\xfd\xfa\x00dummydata'
|
||||||
|
|
||||||
val = self.s.field_get(self.FakeHeader(), fileobj, b'path',
|
val = self.s.field_get(self.FakeHeader(), fileobj, b'path', as_str=False)
|
||||||
as_str=False, null_terminated=True)
|
|
||||||
self.assertEqual(b'\x01\x02\x03\x04\xff\xfe\xfd\xfa', val)
|
self.assertEqual(b'\x01\x02\x03\x04\xff\xfe\xfd\xfa', val)
|
||||||
fileobj.seek.assert_called_with(16, os.SEEK_CUR)
|
fileobj.seek.assert_called_with(16, os.SEEK_CUR)
|
||||||
|
|
||||||
@ -217,7 +216,8 @@ class StructTest(unittest.TestCase):
|
|||||||
fileobj = mock.MagicMock(io.BufferedReader)
|
fileobj = mock.MagicMock(io.BufferedReader)
|
||||||
fileobj.read.return_value = b'\x01\x02\x03\x04\xff\xfe\xfd\xfa\x00dummydata'
|
fileobj.read.return_value = b'\x01\x02\x03\x04\xff\xfe\xfd\xfa\x00dummydata'
|
||||||
|
|
||||||
val = self.s.field_get(self.FakeHeader(), fileobj, b'path', as_str=False)
|
val = self.s.field_get(self.FakeHeader(), fileobj, b'path',
|
||||||
|
as_str=False, null_terminated=False)
|
||||||
self.assertEqual(b'\x01\x02\x03\x04\xff\xfe\xfd\xfa\x00dummydata', val)
|
self.assertEqual(b'\x01\x02\x03\x04\xff\xfe\xfd\xfa\x00dummydata', val)
|
||||||
fileobj.seek.assert_called_with(16, os.SEEK_CUR)
|
fileobj.seek.assert_called_with(16, os.SEEK_CUR)
|
||||||
|
|
||||||
|
|||||||
@ -12,18 +12,16 @@ class BlendFileBlockTest(unittest.TestCase):
|
|||||||
cls.blendfiles = pathlib.Path(__file__).with_name('blendfiles')
|
cls.blendfiles = pathlib.Path(__file__).with_name('blendfiles')
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.bf = None
|
self.bf = blendfile.BlendFile(self.blendfiles / 'basic_file.blend')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if self.bf:
|
if self.bf:
|
||||||
self.bf.close()
|
self.bf.close()
|
||||||
|
|
||||||
def test_loading(self):
|
def test_loading(self):
|
||||||
self.bf = blendfile.BlendFile(self.blendfiles / 'basic_file.blend')
|
|
||||||
self.assertFalse(self.bf.is_compressed)
|
self.assertFalse(self.bf.is_compressed)
|
||||||
|
|
||||||
def test_some_properties(self):
|
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)
|
||||||
|
|
||||||
@ -55,7 +53,6 @@ class BlendFileBlockTest(unittest.TestCase):
|
|||||||
self.assertEqual('MECube³', mname)
|
self.assertEqual('MECube³', mname)
|
||||||
|
|
||||||
def test_get_recursive_iter(self):
|
def test_get_recursive_iter(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]
|
||||||
assert isinstance(ob, blendfile.BlendFileBlock)
|
assert isinstance(ob, blendfile.BlendFileBlock)
|
||||||
|
|
||||||
@ -72,10 +69,82 @@ class BlendFileBlockTest(unittest.TestCase):
|
|||||||
((b'id', b'lib'), 0),
|
((b'id', b'lib'), 0),
|
||||||
((b'id', b'name'), 'OBümlaut'),
|
((b'id', b'name'), 'OBümlaut'),
|
||||||
((b'id', b'flag'), 0),
|
((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.
|
list(gen)[:6])
|
||||||
|
|
||||||
|
def test_iter_recursive(self):
|
||||||
|
ob = self.bf.code_index[b'OB'][0]
|
||||||
|
assert isinstance(ob, blendfile.BlendFileBlock)
|
||||||
|
|
||||||
|
# We can't test all of them in a reliable way, but it shouldn't crash.
|
||||||
|
all_items = list(ob.items_recursive())
|
||||||
|
|
||||||
|
# And we can check the first few items.
|
||||||
|
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'),
|
||||||
|
b'OB\xc3\xbcmlaut'),
|
||||||
|
((b'id', b'flag'), 0),
|
||||||
|
], all_items[:6])
|
||||||
|
|
||||||
|
def test_items(self):
|
||||||
|
ma = self.bf.code_index[b'MA'][0]
|
||||||
|
assert isinstance(ma, blendfile.BlendFileBlock)
|
||||||
|
|
||||||
|
# We can't test all of them in a reliable way, but it shouldn't crash.
|
||||||
|
all_items = list(ma.items())
|
||||||
|
|
||||||
|
# And we can check the first few items.
|
||||||
|
self.assertEqual(
|
||||||
|
[(b'id', '<ID>'), # not recursed into.
|
||||||
|
(b'adt', 0),
|
||||||
|
(b'material_type', 0),
|
||||||
|
(b'flag', 0),
|
||||||
|
(b'r', 0.8000000715255737),
|
||||||
|
(b'g', 0.03218378871679306),
|
||||||
|
(b'b', 0.36836329102516174),
|
||||||
|
(b'specr', 1.0)],
|
||||||
|
all_items[:8])
|
||||||
|
|
||||||
|
def test_keys(self):
|
||||||
|
ma = self.bf.code_index[b'MA'][0]
|
||||||
|
assert isinstance(ma, blendfile.BlendFileBlock)
|
||||||
|
|
||||||
|
# We can't test all of them in a reliable way, but it shouldn't crash.
|
||||||
|
all_keys = list(ma.keys())
|
||||||
|
|
||||||
|
# And we can check the first few items.
|
||||||
|
self.assertEqual(
|
||||||
|
[b'id', b'adt', b'material_type', b'flag', b'r', b'g', b'b', b'specr'],
|
||||||
|
all_keys[:8])
|
||||||
|
|
||||||
|
def test_values(self):
|
||||||
|
ma = self.bf.code_index[b'MA'][0]
|
||||||
|
assert isinstance(ma, blendfile.BlendFileBlock)
|
||||||
|
|
||||||
|
# We can't test all of them in a reliable way, but it shouldn't crash.
|
||||||
|
all_values = list(ma.values())
|
||||||
|
|
||||||
|
# And we can check the first few items.
|
||||||
|
self.assertEqual(
|
||||||
|
['<ID>',
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0.8000000715255737,
|
||||||
|
0.03218378871679306,
|
||||||
|
0.36836329102516174,
|
||||||
|
1.0],
|
||||||
|
all_values[:8])
|
||||||
|
|
||||||
|
def test_get_via_dict_interface(self):
|
||||||
|
ma = self.bf.code_index[b'MA'][0]
|
||||||
|
assert isinstance(ma, blendfile.BlendFileBlock)
|
||||||
|
self.assertEqual(0.8000000715255737, ma[b'r'])
|
||||||
|
|
||||||
|
ob = self.bf.code_index[b'OB'][0]
|
||||||
|
assert isinstance(ob, blendfile.BlendFileBlock)
|
||||||
|
self.assertEqual('OBümlaut', ob[b'id', b'name'].decode())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user