load SDNA structures

master
Josiah 2021-07-23 08:37:59 -05:00
parent 01c051ce14
commit a1d8ba6b8e
No known key found for this signature in database
GPG Key ID: C7BB8573A4ABC4B9
1 changed files with 102 additions and 9 deletions

View File

@ -11,6 +11,20 @@ class Blendfile(io.FileIO):
self.read_header()
# Offsets of each file block by code.
self._block_offsets = self._read_block_offsets()
# SDNA structures by name.
self._sdna = self._load_sdna()
def _read_c_string(self):
"""
Utility method to read a null-terminated C-string.
"""
res = bytes("", "utf-8")
null_char = bytes("\0", "utf-8")
while True:
char = self.read(1)
if char == null_char:
return res.decode("utf-8")
res += char
def _read_block_offsets(self):
"""
@ -37,18 +51,97 @@ class Blendfile(io.FileIO):
return block_offsets
def _read_sdna_indexes(self):
def _load_sdna(self):
"""
Cache the index and offset of each SDNA structure name.
Load each SDNA structure.
"""
pass
try:
offset = self._block_offsets["DNA1"]
except KeyError:
raise ValueError("Missing DNA1 file block.")
def _load_sdna(self, sdna_index):
sdna = {}
# Skip file block header.
self.seek(offset)
# File block code.
self.seek(4, io.SEEK_CUR)
# Size of file block after this header.
block_size = int.from_bytes(self.read(4), self.endianness)
self.seek(8 + self.pointer_size, io.SEEK_CUR)
block_end = offset + block_size
# Identifier; should be "SDNA"
self.seek(4, io.SEEK_CUR)
# Name; should be "NAME"
self.seek(4, io.SEEK_CUR)
# List of structure names.
total_names = int.from_bytes(self.read(4), self.endianness)
names = []
for _ in range(total_names):
names.append(self._read_c_string())
sdna["names"] = names
# List of types
# Align at 4 bytes.
if self.tell() % 4 != 0:
self.seek(4 - (self.tell() % 4), io.SEEK_CUR)
# Type identifier; should be "TYPE"
type_identifier = self.read(4).decode("utf-8")
assert(type_identifier == "TYPE")
# Number of types follows.
total_types = int.from_bytes(self.read(4), self.endianness)
# Avoiding collision with builtin types module.
_types = []
for _ in range(total_types):
_types.append(self._read_c_string())
sdna["types"] = _types
# Length of each type.
# Align at 4 bytes.
if self.tell() % 4 != 0:
self.seek(4 - (self.tell() % 4), io.SEEK_CUR)
# Type length identifier; should be "TLEN"
len_identifier = self.read(4).decode("utf-8")
assert(len_identifier == "TLEN")
type_lengths = []
for _ in range(total_types):
type_lengths.append(int.from_bytes(self.read(2), self.endianness))
sdna["tlen"] = type_lengths
# Align at 4 bytes.
if self.tell() % 4 != 0:
self.seek(4 - (self.tell() % 4), io.SEEK_CUR)
# Structure identifier; should be "STRC".
struct_identifier = self.read(4).decode("utf-8")
assert(struct_identifier == "STRC")
structs = {}
# Number of structures follows.
total_structs = int.from_bytes(self.read(4), self.endianness)
for _ in range(total_structs):
# Index in types containing the name of the structure.
type_index = int.from_bytes(self.read(2), self.endianness)
fields = {}
# Number of fields in this structure.
total_fields = int.from_bytes(self.read(2), self.endianness)
for _ in range(total_fields):
# Index in type
field_type = int.from_bytes(self.read(2), self.endianness)
# Index in name
field_name = int.from_bytes(self.read(2), self.endianness)
fields[names[field_name]] = _types[field_type]
structs[_types[type_index]] = fields
sdna["structs"] = structs
return sdna
def _load_struct(self, struct_name):
"""
Load an SDNA struct as a dict describing the structures.
Load a struct according to the SDNA.
"""
# We could consider caching recently loaded SDNA structs.
pass
fields = self._sdna["structs"][struct_name]
print(fields)
return {}
def read_header(self):
self.seek(0)
@ -105,9 +198,9 @@ class Blendfile(io.FileIO):
sdna_index = int.from_bytes(self.read(4), self.endianness)
count = int.from_bytes(self.read(4), self.endianness)
sdna_struct = self._load_sdna(sdna_index)
struct_name = list(self._sdna["structs"])[sdna_index]
# TODO load structs according to SDNA.
for _ in range(count):
translated = {}
translated = self._load_struct(struct_name)
yield translated