bledfile.py: Port over changes from src_utils repo.
commit 45e3ba4a4f8fb39d2a090f1f67c10d6132939d8e blendfile.py: add BlendFileBlock.get_data_hash() 'pointer-invariant' hash generator. This aims at giving some kind of uid to replace addr_old, that does not changes on every .blend saving. commit 81c92e60195f9ff3ce327c26278d07eafb4a0596 blendfile.py: fix/add some 'basic data' reading (mostly arrays of ints/floats/...), add utils to recursively get all items of a struct. New `BlendFileBlock.items_recursive_iter()` will yield all valid key/values pair of a given struct, including sub ones (e.g. 'id.next', 'id.name', etc.).
This commit is contained in:
@@ -410,6 +410,46 @@ class BlendFileBlock:
|
|||||||
use_nil=use_nil, use_str=use_str,
|
use_nil=use_nil, use_str=use_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_recursive_iter(self, path, path_root=b"",
|
||||||
|
default=...,
|
||||||
|
sdna_index_refine=None,
|
||||||
|
use_nil=True, use_str=True,
|
||||||
|
base_index=0,
|
||||||
|
):
|
||||||
|
path_full = path_root + b"." + path if path_root else path
|
||||||
|
try:
|
||||||
|
yield (path_full, self.get(path_full, default, sdna_index_refine, use_nil, use_str, base_index))
|
||||||
|
except NotImplementedError as err:
|
||||||
|
msg, dna_name, dna_type = err.args
|
||||||
|
struct_index = self.file.sdna_index_from_id.get(dna_type.dna_type_id, None)
|
||||||
|
if struct_index is None:
|
||||||
|
yield (path_full, "<%s>" % dna_type.dna_type_id.decode('ascii'))
|
||||||
|
else:
|
||||||
|
struct = self.file.structs[struct_index]
|
||||||
|
for f in struct.fields:
|
||||||
|
yield from self.get_recursive_iter(f.dna_name.name_only, path_full, default, None, use_nil, use_str, 0)
|
||||||
|
|
||||||
|
def items_recursive_iter(self):
|
||||||
|
for k in self.keys():
|
||||||
|
yield from self.get_recursive_iter(k, use_str=False)
|
||||||
|
|
||||||
|
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
|
||||||
|
file load & save (i.e. it does not changes due to pointer addresses variations).
|
||||||
|
"""
|
||||||
|
# TODO This implementation is most likely far from optimal... and CRC32 is not renown as the best hashing
|
||||||
|
# algo either. But for now does the job!
|
||||||
|
import zlib
|
||||||
|
def _is_pointer(self, k):
|
||||||
|
return self.file.structs[self.sdna_index].field_from_path(self.file.header, self.file.handle, k).dna_name.is_pointer
|
||||||
|
|
||||||
|
hsh = 1
|
||||||
|
for k, v in self.items_recursive_iter():
|
||||||
|
if not _is_pointer(self, k):
|
||||||
|
hsh = zlib.adler32(str(v).encode(), hsh)
|
||||||
|
return hsh
|
||||||
|
|
||||||
def set(self, path, value,
|
def set(self, path, value,
|
||||||
sdna_index_refine=None,
|
sdna_index_refine=None,
|
||||||
):
|
):
|
||||||
@@ -470,14 +510,16 @@ class BlendFileBlock:
|
|||||||
try:
|
try:
|
||||||
yield self[k]
|
yield self[k]
|
||||||
except NotImplementedError as err:
|
except NotImplementedError as err:
|
||||||
yield ...
|
msg, dna_name, dna_type = err.args
|
||||||
|
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 err:
|
except NotImplementedError as err:
|
||||||
yield (k, ...)
|
msg, dna_name, dna_type = err.args
|
||||||
|
yield (k, "<%s>" % dna_type.dna_type_id.decode('ascii'))
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -692,10 +734,20 @@ class DNAStruct:
|
|||||||
if dna_name.is_pointer:
|
if dna_name.is_pointer:
|
||||||
return DNA_IO.read_pointer(handle, header)
|
return DNA_IO.read_pointer(handle, header)
|
||||||
elif dna_type.dna_type_id == b'int':
|
elif dna_type.dna_type_id == b'int':
|
||||||
|
if dna_name.array_size > 1:
|
||||||
|
return [DNA_IO.read_int(handle, header) for i in range(dna_name.array_size)]
|
||||||
return DNA_IO.read_int(handle, header)
|
return DNA_IO.read_int(handle, header)
|
||||||
elif dna_type.dna_type_id == b'short':
|
elif dna_type.dna_type_id == b'short':
|
||||||
|
if dna_name.array_size > 1:
|
||||||
|
return [DNA_IO.read_short(handle, header) for i in range(dna_name.array_size)]
|
||||||
return DNA_IO.read_short(handle, header)
|
return DNA_IO.read_short(handle, header)
|
||||||
|
elif dna_type.dna_type_id == b'uint64_t':
|
||||||
|
if dna_name.array_size > 1:
|
||||||
|
return [DNA_IO.read_ulong(handle, header) for i in range(dna_name.array_size)]
|
||||||
|
return DNA_IO.read_ulong(handle, header)
|
||||||
elif dna_type.dna_type_id == b'float':
|
elif dna_type.dna_type_id == b'float':
|
||||||
|
if dna_name.array_size > 1:
|
||||||
|
return [DNA_IO.read_float(handle, header) for i in range(dna_name.array_size)]
|
||||||
return DNA_IO.read_float(handle, header)
|
return DNA_IO.read_float(handle, header)
|
||||||
elif dna_type.dna_type_id == b'char':
|
elif dna_type.dna_type_id == b'char':
|
||||||
if use_str:
|
if use_str:
|
||||||
@@ -709,7 +761,7 @@ class DNAStruct:
|
|||||||
else:
|
else:
|
||||||
return DNA_IO.read_bytes(handle, dna_name.array_size)
|
return DNA_IO.read_bytes(handle, dna_name.array_size)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("%r exists but isn't pointer, can't resolve field %r" % (path, dna_name.name_only))
|
raise NotImplementedError("%r exists but isn't pointer, can't resolve field %r" % (path, dna_name.name_only), dna_name, dna_type)
|
||||||
|
|
||||||
def field_set(self, header, handle, path, value):
|
def field_set(self, header, handle, path, value):
|
||||||
assert(type(path) == bytes)
|
assert(type(path) == bytes)
|
||||||
@@ -727,7 +779,7 @@ class DNAStruct:
|
|||||||
else:
|
else:
|
||||||
return DNA_IO.write_bytes(handle, value, dna_name.array_size)
|
return DNA_IO.write_bytes(handle, value, dna_name.array_size)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Setting %r is not yet supported" % dna_type[0])
|
raise NotImplementedError("Setting %r is not yet supported" % dna_type[0], dna_name, dna_type)
|
||||||
|
|
||||||
|
|
||||||
class DNA_IO:
|
class DNA_IO:
|
||||||
@@ -796,6 +848,13 @@ class DNA_IO:
|
|||||||
st = DNA_IO.USHORT[fileheader.endian_index]
|
st = DNA_IO.USHORT[fileheader.endian_index]
|
||||||
return st.unpack(handle.read(st.size))[0]
|
return st.unpack(handle.read(st.size))[0]
|
||||||
|
|
||||||
|
SSHORT = struct.Struct(b'<h'), struct.Struct(b'>h')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def read_short(handle, fileheader):
|
||||||
|
st = DNA_IO.SSHORT[fileheader.endian_index]
|
||||||
|
return st.unpack(handle.read(st.size))[0]
|
||||||
|
|
||||||
UINT = struct.Struct(b'<I'), struct.Struct(b'>I')
|
UINT = struct.Struct(b'<I'), struct.Struct(b'>I')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -810,15 +869,11 @@ class DNA_IO:
|
|||||||
st = DNA_IO.SINT[fileheader.endian_index]
|
st = DNA_IO.SINT[fileheader.endian_index]
|
||||||
return st.unpack(handle.read(st.size))[0]
|
return st.unpack(handle.read(st.size))[0]
|
||||||
|
|
||||||
|
FLOAT = struct.Struct(b'<f'), struct.Struct(b'>f')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read_float(handle, fileheader):
|
def read_float(handle, fileheader):
|
||||||
return struct.unpack(fileheader.endian_str + b'f', handle.read(4))[0]
|
st = DNA_IO.FLOAT[fileheader.endian_index]
|
||||||
|
|
||||||
SSHORT = struct.Struct(b'<h'), struct.Struct(b'>h')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def read_short(handle, fileheader):
|
|
||||||
st = DNA_IO.SSHORT[fileheader.endian_index]
|
|
||||||
return st.unpack(handle.read(st.size))[0]
|
return st.unpack(handle.read(st.size))[0]
|
||||||
|
|
||||||
ULONG = struct.Struct(b'<Q'), struct.Struct(b'>Q')
|
ULONG = struct.Struct(b'<Q'), struct.Struct(b'>Q')
|
||||||
|
Reference in New Issue
Block a user