diff --git a/scripts/modules/blend_render_info.py b/scripts/modules/blend_render_info.py index 1d2b60619b1..9ec9045f0e3 100755 --- a/scripts/modules/blend_render_info.py +++ b/scripts/modules/blend_render_info.py @@ -4,20 +4,14 @@ # SPDX-License-Identifier: GPL-2.0-or-later # This module can get render info without running from inside blender. -# -# This struct won't change according to Ton. -# Note that the size differs on 32/64bit -# -# typedef struct BHead { -# int code, len; -# void *old; -# int SDNAnr, nr; -# } BHead; + __all__ = ( "read_blend_rend_chunk", ) +import blendfile_header + class RawBlendFileReader: """ @@ -64,75 +58,51 @@ class RawBlendFileReader: return False +def get_render_info_structure(endian_str, size): + import struct + # The maximum size of the scene name changed over time, so create a different + # structure depending on the size of the entire block. + if size == 2 * 4 + 24: + return struct.Struct(endian_str + b'ii24s') + if size == 2 * 4 + 64: + return struct.Struct(endian_str + b'ii64s') + if size == 2 * 4 + 256: + return struct.Struct(endian_str + b'ii256s') + raise ValueError("Unknown REND chunk size: {:d}".format(size)) + + def _read_blend_rend_chunk_from_file(blendfile, filepath): import struct import sys from os import SEEK_CUR - head = blendfile.read(7) - if head != b'BLENDER': + try: + blender_header = blendfile_header.BlendFileHeader(blendfile) + except blendfile_header.BlendHeaderError: sys.stderr.write("Not a blend file: {:s}\n".format(filepath)) return [] - is_64_bit = (blendfile.read(1) == b'-') - - # true for PPC, false for X86 - is_big_endian = (blendfile.read(1) == b'V') - - # Now read the bhead chunk! - blendfile.seek(3, SEEK_CUR) # Skip the version. - scenes = [] - sizeof_bhead = 24 if is_64_bit else 20 + endian_str = b'<' if blender_header.is_little_endian else b'>' - # Should always be 4, but a malformed/corrupt file may be less. - while (bhead_id := blendfile.read(4)) != b'ENDB': - - if len(bhead_id) != 4: - sys.stderr.write("Unable to read until ENDB block (corrupt file): {:s}\n".format(filepath)) + block_header_struct = blender_header.create_block_header_struct() + while bhead := blendfile_header.BlockHeader(blendfile, block_header_struct): + if bhead.code == b'ENDB': break - - sizeof_data_left = struct.unpack('>i' if is_big_endian else '2i' if is_big_endian else '<2i', blendfile.read(8)) - sizeof_data_left -= 8 - - scene_name = blendfile.read(64) - sizeof_data_left -= 64 - if b'\0' not in scene_name: - if sizeof_data_left >= 192: - # Assume new, up to 256 bytes name. - scene_name += blendfile.read(192) - sizeof_data_left -= 192 - if b'\0' not in scene_name: - scene_name = scene_name[:-1] + b'\0' + remaining_bytes = bhead.size + if bhead.code == b'REND': + rend_block_struct = get_render_info_structure(endian_str, bhead.size) + start_frame, end_frame, scene_name = rend_block_struct.unpack(blendfile.read(rend_block_struct.size)) + remaining_bytes -= rend_block_struct.size scene_name = scene_name[:scene_name.index(b'\0')] # It's possible old blend files are not UTF8 compliant, use `surrogateescape`. scene_name = scene_name.decode("utf8", errors="surrogateescape") - scenes.append((start_frame, end_frame, scene_name)) - if sizeof_data_left > 0: - blendfile.seek(sizeof_data_left, SEEK_CUR) - elif sizeof_data_left < 0: - # Very unlikely, but prevent attempting to further parse corrupt data. - sys.stderr.write("Error calculating next block (corrupt file): {:s}\n".format(filepath)) - break + blendfile.seek(remaining_bytes, SEEK_CUR) return scenes diff --git a/scripts/modules/blendfile_header.py b/scripts/modules/blendfile_header.py new file mode 100644 index 00000000000..4c1e8d72331 --- /dev/null +++ b/scripts/modules/blendfile_header.py @@ -0,0 +1,234 @@ +# SPDX-FileCopyrightText: 2025 Blender Authors +# +# SPDX-License-Identifier: GPL-2.0-or-later + +''' +This module contains utility classes for reading headers in .blend files. + +This is a pure Python implementation of the corresponding C++ code in Blender +in BLO_core_blend_header.hh and BLO_core_bhead.hh. +''' + +import os +import struct +import typing + +from dataclasses import dataclass + + +class BlendHeaderError(Exception): + pass + + +@dataclass +class BHead4: + code: bytes + len: int + old: int + SDNAnr: int + nr: int + + +@dataclass +class SmallBHead8: + code: bytes + len: int + old: int + SDNAnr: int + nr: int + + +@dataclass +class LargeBHead8: + code: bytes + SDNAnr: int + old: int + len: int + nr: int + + +@dataclass +class BlockHeaderStruct: + # Binary format of the encoded header. + struct: struct.Struct + # Corresponding Python type for retrieving block header values. + type: typing.Type[typing.Union[BHead4, SmallBHead8, LargeBHead8]] + + @property + def size(self) -> int: + return self.struct.size + + def parse(self, data: bytes) -> typing.Union[BHead4, SmallBHead8, LargeBHead8]: + return self.type(*self.struct.unpack(data)) + + +class BlendFileHeader: + """ + BlendFileHeader represents the first 12-17 bytes of a blend file. + + It contains information about the hardware architecture, which is relevant + to the structure of the rest of the file. + """ + + # Always 'BLENDER'. + magic: bytes + # Currently always 0 or 1. + file_format_version: int + # Either 4 or 8. + pointer_size: int + # Endianness of values stored in the file. + is_little_endian: bool + # Blender version the file has been written with. + # The last two digits are the minor version. So 280 is 2.80. + version: int + + def __init__(self, file: typing.IO[bytes]) -> None: + file.seek(0, os.SEEK_SET) + + bytes_0_6 = file.read(7) + if bytes_0_6 != b'BLENDER': + raise BlendHeaderError("invalid first bytes %r" % bytes_0_6) + self.magic = bytes_0_6 + + byte_7 = file.read(1) + is_legacy_header = byte_7 in (b'_', b'-') + if is_legacy_header: + self.file_format_version = 0 + if byte_7 == b'_': + self.pointer_size = 4 + elif byte_7 == b'-': + self.pointer_size = 8 + else: + raise BlendHeaderError("invalid pointer size %r" % byte_7) + byte_8 = file.read(1) + if byte_8 == b'v': + self.is_little_endian = True + elif byte_8 == b'V': + self.is_little_endian = False + else: + raise BlendHeaderError("invalid endian indicator %r" % byte_8) + bytes_9_11 = file.read(3) + self.version = int(bytes_9_11) + else: + byte_8 = file.read(1) + header_size = int(byte_7 + byte_8) + if header_size != 17: + raise BlendHeaderError("unknown file header size %d" % header_size) + byte_9 = file.read(1) + if byte_9 != b'-': + raise BlendHeaderError("invalid file header") + self.pointer_size = 8 + byte_10_11 = file.read(2) + self.file_format_version = int(byte_10_11) + if self.file_format_version != 1: + raise BlendHeaderError("unsupported file format version %r" % self.file_format_version) + byte_12 = file.read(1) + if byte_12 != b'v': + raise BlendHeaderError("invalid file header") + self.is_little_endian = True + byte_13_16 = file.read(4) + self.version = int(byte_13_16) + + def create_block_header_struct(self) -> BlockHeaderStruct: + assert self.file_format_version in (0, 1) + endian_str = b'<' if self.is_little_endian else b'>' + if self.file_format_version == 1: + header_struct = struct.Struct(b''.join(( + endian_str, + # LargeBHead8.code + b'4s', + # LargeBHead8.SDNAnr + b'i', + # LargeBHead8.old + b'Q', + # LargeBHead8.len + b'q', + # LargeBHead8.nr + b'q', + ))) + return BlockHeaderStruct(header_struct, LargeBHead8) + + if self.pointer_size == 4: + header_struct = struct.Struct(b''.join(( + endian_str, + # BHead4.code + b'4s', + # BHead4.len + b'i', + # BHead4.old + b'I', + # BHead4.SDNAnr + b'i', + # BHead4.nr + b'i', + ))) + return BlockHeaderStruct(header_struct, BHead4) + + assert self.pointer_size == 8 + header_struct = struct.Struct(b''.join(( + endian_str, + # SmallBHead8.code + b'4s', + # SmallBHead8.len + b'i', + # SmallBHead8.old + b'Q', + # SmallBHead8.SDNAnr + b'i', + # SmallBHead8.nr + b'i', + ))) + return BlockHeaderStruct(header_struct, SmallBHead8) + + +class BlockHeader: + """ + A .blend file consists of a sequence of blocks whereby each block has a header. + This class can parse a header block in a specific .blend file. + + Note the binary representation of this header is different for different files. + This class provides a unified interface for these underlying representations. + """ + + __slots__ = ( + "code", + "size", + "addr_old", + "sdna_index", + "count", + ) + + # Indicates the type of the block. See BLO_CODE_* in BLO_core_bhead.hh. + code: bytes + # Number of bytes in the block. + size: int + # Old pointer/identifier of the block. + addr_old: int + # DNA struct index of the data in the block. + sdna_index: int + # Number of DNA structures in the block. + count: int + + def __init__(self, file: typing.IO[bytes], block_header_struct: BlockHeaderStruct) -> None: + data = file.read(block_header_struct.size) + + if len(data) != block_header_struct.size: + if len(data) != 8: + raise BlendHeaderError("invalid block header size") + legacy_endb = struct.Struct(b'4sI') + endb_header = legacy_endb.unpack(data) + if endb_header[0] != b'ENDB': + raise BlendHeaderError("invalid block header") + self.code = b'ENDB' + self.size = 0 + self.addr_old = 0 + self.sdna_index = 0 + self.count = 0 + return + + header = block_header_struct.parse(data) + self.code = header.code.partition(b'\0')[0] + self.size = header.len + self.addr_old = header.old + self.sdna_index = header.SDNAnr + self.count = header.nr diff --git a/tests/files/io_tests/blend_parsing/BHead4.blend b/tests/files/io_tests/blend_parsing/BHead4.blend new file mode 100644 index 00000000000..29d04afb842 --- /dev/null +++ b/tests/files/io_tests/blend_parsing/BHead4.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbf99fe754bb426dd69fa211d6e80b4991728f1f8c201a547f7949b793edf3c2 +size 58413 diff --git a/tests/files/io_tests/blend_parsing/BHead4_big_endian.blend b/tests/files/io_tests/blend_parsing/BHead4_big_endian.blend new file mode 100644 index 00000000000..058d95d9319 --- /dev/null +++ b/tests/files/io_tests/blend_parsing/BHead4_big_endian.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9924268b6e5b494dadc482e8b87314f2b28b7371c8c76765ec8f4d3cb9c6cfac +size 30032 diff --git a/tests/files/io_tests/blend_parsing/LargeBHead8.blend b/tests/files/io_tests/blend_parsing/LargeBHead8.blend new file mode 100644 index 00000000000..bea651ed788 --- /dev/null +++ b/tests/files/io_tests/blend_parsing/LargeBHead8.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99419bb72544c1152e4b8dce9dbfc8febc02f480c98fc29b56d20a941816e298 +size 512735 diff --git a/tests/files/io_tests/blend_parsing/SmallBHead8.blend b/tests/files/io_tests/blend_parsing/SmallBHead8.blend new file mode 100644 index 00000000000..c470ec27563 --- /dev/null +++ b/tests/files/io_tests/blend_parsing/SmallBHead8.blend @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d8760efa9d61eb49d27a69f69b296f54a7164cac5dccaf5e2e2a2644ec9ae645 +size 114063 diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 8a6f0d57e25..2f1b4711b9a 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -340,6 +340,12 @@ if(TEST_SRC_DIR_EXISTS) --output-dir ${TEST_OUT_DIR}/blendfile_io/ --test-dir "${TEST_SRC_DIR}/libraries_and_linking" ) + + add_blender_test( + blendfile_header + --python ${CMAKE_CURRENT_LIST_DIR}/bl_blendfile_header.py -- + --testdir "${TEST_SRC_DIR}/io_tests/blend_parsing" + ) endif() # ------------------------------------------------------------------------------ diff --git a/tests/python/bl_blendfile_header.py b/tests/python/bl_blendfile_header.py new file mode 100644 index 00000000000..8a2b172dd3b --- /dev/null +++ b/tests/python/bl_blendfile_header.py @@ -0,0 +1,168 @@ +# SPDX-FileCopyrightText: 2025 Blender Authors +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import blendfile_header +import blend_render_info +import bpy +import pathlib +import sys +import unittest +import gzip +import tempfile + +args = None + + +class BlendFileHeaderTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.testdir = args.testdir + + def setUp(self): + self.assertTrue(self.testdir.exists(), + "Test dir {0} should exist".format(self.testdir)) + + def test_small_bhead_8(self): + path = self.testdir / "SmallBHead8.blend" + with gzip.open(path, "rb") as f: + header = blendfile_header.BlendFileHeader(f) + self.assertEqual(header.magic, b"BLENDER") + self.assertEqual(header.file_format_version, 0) + self.assertEqual(header.pointer_size, 8) + self.assertTrue(header.is_little_endian) + self.assertEqual(header.version, 300) + + header_struct = header.create_block_header_struct() + self.assertIs(header_struct.type, blendfile_header.SmallBHead8) + + buffer = f.read(header_struct.struct.size) + block = header_struct.parse(buffer) + self.assertEqual(block.code, b"REND") + self.assertEqual(block.len, 72) + self.assertEqual(block.old, 140732920740000) + self.assertEqual(block.SDNAnr, 0) + self.assertEqual(block.nr, 1) + + self.assertEqual(blend_render_info.read_blend_rend_chunk(path), [(1, 250, "Scene")]) + + def test_large_bhead_8(self): + path = self.testdir / "LargeBHead8.blend" + with open(path, "rb") as f: + header = blendfile_header.BlendFileHeader(f) + self.assertEqual(header.magic, b"BLENDER") + self.assertEqual(header.file_format_version, 1) + self.assertEqual(header.pointer_size, 8) + self.assertTrue(header.is_little_endian) + self.assertEqual(header.version, 500) + + header_struct = header.create_block_header_struct() + self.assertIs(header_struct.type, blendfile_header.LargeBHead8) + + buffer = f.read(header_struct.struct.size) + block = header_struct.parse(buffer) + self.assertEqual(block.code, b"REND") + self.assertEqual(block.len, 72) + self.assertEqual(block.old, 140737488337232) + self.assertEqual(block.SDNAnr, 0) + self.assertEqual(block.nr, 1) + + self.assertEqual(blend_render_info.read_blend_rend_chunk(path), [(1, 250, "Scene")]) + + def test_bhead_4(self): + path = self.testdir / "BHead4.blend" + with gzip.open(path, "rb") as f: + header = blendfile_header.BlendFileHeader(f) + self.assertEqual(header.magic, b"BLENDER") + self.assertEqual(header.file_format_version, 0) + self.assertEqual(header.pointer_size, 4) + self.assertTrue(header.is_little_endian) + self.assertEqual(header.version, 260) + + header_struct = header.create_block_header_struct() + self.assertIs(header_struct.type, blendfile_header.BHead4) + + buffer = f.read(header_struct.struct.size) + block = header_struct.parse(buffer) + self.assertEqual(block.code, b"REND") + self.assertEqual(block.len, 32) + self.assertEqual(block.old, 2684488) + self.assertEqual(block.SDNAnr, 0) + self.assertEqual(block.nr, 1) + + self.assertEqual(blend_render_info.read_blend_rend_chunk(path), [(1, 250, "Space types")]) + + def test_bhead_4_big_endian(self): + path = self.testdir / "BHead4_big_endian.blend" + with gzip.open(path, "rb") as f: + header = blendfile_header.BlendFileHeader(f) + self.assertEqual(header.magic, b"BLENDER") + self.assertEqual(header.file_format_version, 0) + self.assertEqual(header.pointer_size, 4) + self.assertFalse(header.is_little_endian) + self.assertEqual(header.version, 170) + + header_struct = header.create_block_header_struct() + self.assertIs(header_struct.type, blendfile_header.BHead4) + + buffer = f.read(header_struct.struct.size) + block = header_struct.parse(buffer) + self.assertEqual(block.code, b"REND") + self.assertEqual(block.len, 32) + self.assertEqual(block.old, 2147428916) + self.assertEqual(block.SDNAnr, 0) + self.assertEqual(block.nr, 1) + + self.assertEqual(blend_render_info.read_blend_rend_chunk(path), [(1, 150, "1")]) + + def test_current(self): + directory = tempfile.mkdtemp() + path = pathlib.Path(directory) / "test.blend" + + bpy.ops.wm.read_factory_settings(use_empty=True) + + scene = bpy.data.scenes[0] + scene.name = "Test Scene" + scene.frame_start = 10 + scene.frame_end = 20 + bpy.ops.wm.save_as_mainfile(filepath=str(path), compress=False, copy=True) + + version = bpy.app.version + version_int = version[0] * 100 + version[1] + + with open(path, "rb") as f: + header = blendfile_header.BlendFileHeader(f) + self.assertEqual(header.magic, b"BLENDER") + self.assertEqual(header.file_format_version, 1) + self.assertEqual(header.pointer_size, 8) + self.assertTrue(header.is_little_endian) + self.assertEqual(header.version, version_int) + + header_struct = header.create_block_header_struct() + self.assertIs(header_struct.type, blendfile_header.LargeBHead8) + + buffer = f.read(header_struct.struct.size) + block = header_struct.parse(buffer) + self.assertEqual(block.code, b"REND") + + self.assertEqual(blend_render_info.read_blend_rend_chunk(path), [(10, 20, "Test Scene")]) + + +def main(): + global args + import argparse + + if '--' in sys.argv: + argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:] + else: + argv = sys.argv + + parser = argparse.ArgumentParser() + parser.add_argument('--testdir', required=True, type=pathlib.Path) + args, remaining = parser.parse_known_args(argv) + + unittest.main(argv=remaining) + + +if __name__ == "__main__": + main() diff --git a/tests/python/bl_blendfile_versioning.py b/tests/python/bl_blendfile_versioning.py index 2d5486bcc04..8be73da1fb1 100644 --- a/tests/python/bl_blendfile_versioning.py +++ b/tests/python/bl_blendfile_versioning.py @@ -272,6 +272,12 @@ class TestBlendFileOpenLinkSaveAllTestFiles(TestHelper): (OSError, RuntimeError), "created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0" ), + # io_tests/blend_parsing/BHead4_big_endian.blend + # File generated from a big endian build of Blender. + "BHead4_big_endian.blend": ( + (OSError, RuntimeError), + "created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0" + ), } assert all(p.endswith("/") for p in self.excluded_open_link_dirs) diff --git a/tools/modules/blendfile.py b/tools/modules/blendfile.py index 15be8792959..146ede209f6 100644 --- a/tools/modules/blendfile.py +++ b/tools/modules/blendfile.py @@ -18,11 +18,13 @@ __all__ = ( "BlendFileRaw", ) +import os +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "scripts", "modules")) -from collections import namedtuple +import blendfile_header import gzip import logging -import os import struct import tempfile import zstandard as zstd @@ -55,10 +57,8 @@ class BlendFile: "filepath_orig", # BlendFileHeader "header", - # struct.Struct + # blendfile_header.BlockHeaderStruct "block_header_struct", - # namedtuple - "block_header_fields", # BlendFileBlock "blocks", # [DNAStruct, ...] @@ -80,7 +80,7 @@ class BlendFile: log.debug("initializing reading blend-file") self.handle = handle self.header = BlendFileHeader(handle) - self.block_header_struct, self.block_header_fields = self.header.create_block_header_struct() + self.block_header_struct = self.header.create_block_header_struct() self.blocks = [] self.code_index = {} self.structs = [] @@ -245,18 +245,13 @@ class BlendFile: return structs, sdna_index_from_id -class BlendFileBlock: +class BlendFileBlock(blendfile_header.BlockHeader): """ Instance of a struct. """ __slots__ = ( # BlendFile "file", - "code", - "size", - "addr_old", - "sdna_index", - "count", "file_offset", "user_data", ) @@ -273,46 +268,10 @@ class BlendFileBlock: )) def __init__(self, handle, bfile): + super().__init__(handle, bfile.block_header_struct) self.file = bfile self.user_data = None - - data = handle.read(bfile.block_header_struct.size) - - if len(data) != bfile.block_header_struct.size: - print("WARNING! Blend file seems to be badly truncated!") - self.code = b'ENDB' - self.size = 0 - self.addr_old = 0 - self.sdna_index = 0 - self.count = 0 - self.file_offset = 0 - return - # Header can be just 8 byte because of ENDB block in old .blend files. - if len(data) > 8: - blockheader = bfile.block_header_fields(*bfile.block_header_struct.unpack(data)) - self.code = blockheader[0].partition(b'\0')[0] - if self.code != b'ENDB': - self.size = blockheader.len - self.addr_old = blockheader.old - self.sdna_index = blockheader.SDNAnr - self.count = blockheader.nr - self.file_offset = handle.tell() - else: - self.size = 0 - self.addr_old = 0 - self.sdna_index = 0 - self.count = 0 - self.file_offset = 0 - else: - OLDBLOCK = struct.Struct(b'4sI') - blockheader = OLDBLOCK.unpack(data) - self.code = blockheader[0].partition(b'\0')[0] - self.code = DNA_IO.read_data0(blockheader[0]) - self.size = 0 - self.addr_old = 0 - self.sdna_index = 0 - self.count = 0 - self.file_offset = 0 + self.file_offset = handle.tell() @property def dna_type(self): @@ -744,74 +703,12 @@ class BlendFileBlockRaw: # Read Magic -class BlendFileHeader: - """ - BlendFileHeader allocates the first 12-17 bytes (depending on the file version) of a blend file - it contains information about the hardware architecture - """ - __slots__ = ( - # str - "magic", - # int - "file_format_version", - # int 4/8 - "pointer_size", - # bool - "is_little_endian", - # int - "version", - # str, used to pass to 'struct' - "endian_str", - # int, used to index common types - "endian_index", - ) +class BlendFileHeader(blendfile_header.BlendFileHeader): + endian_index: int + endian_str: bytes def __init__(self, handle): - log.debug("reading blend-file-header") - - bytes_0_6 = handle.read(7) - if bytes_0_6 != b'BLENDER': - raise BlendFileError("invalid first bytes") - self.magic = bytes_0_6 - - byte_7 = handle.read(1) - is_legacy_header = byte_7 in (b'_', b'-') - if is_legacy_header: - self.file_format_version = 0 - if byte_7 == b'_': - self.pointer_size = 4 - elif byte_7 == b'-': - self.pointer_size = 8 - else: - raise BlendFileError("invalid file header") - byte_8 = handle.read(1) - if byte_8 == b'v': - self.is_little_endian = True - elif byte_8 == b'V': - self.is_little_endian = False - else: - raise BlendFileError("invalid file header") - bytes_9_11 = handle.read(3) - self.version = int(bytes_9_11) - else: - byte_8 = handle.read(1) - header_size = int(byte_7 + byte_8) - if header_size != 17: - raise BlendFileError("invalid file header") - byte_9 = handle.read(1) - if byte_9 != b'-': - raise BlendFileError("invalid file header") - self.pointer_size = 8 - byte_10_11 = handle.read(2) - self.file_format_version = int(byte_10_11) - if self.file_format_version != 1: - raise BlendFileError("unsupported file format version") - byte_12 = handle.read(1) - if byte_12 != b'v': - raise BlendFileError("invalid file header") - self.is_little_endian = True - byte_13_16 = handle.read(4) - self.version = int(byte_13_16) + super().__init__(handle) if self.is_little_endian: self.endian_str = b'<' @@ -820,51 +717,6 @@ class BlendFileHeader: self.endian_index = 1 self.endian_str = b'>' - def create_block_header_struct(self): - if self.file_format_version == 0: - if self.pointer_size == 4: - return struct.Struct(b''.join(( - self.endian_str, - # BHead4.code - b'4s', - # BHead4.len - b'i', - # BHead4.old - b'I', - # BHead4.SDNAnr - b'i', - # BHead4.nr - b'i', - ))), namedtuple('BHead4', ('code', 'len', 'old', 'SDNAnr', 'nr')) - else: - return struct.Struct(b''.join(( - self.endian_str, - # SmallBHead8.code - b'4s', - # SmallBHead8.len - b'i', - # SmallBHead8.old - b'Q', - # SmallBHead8.SDNAnr - b'i', - # SmallBHead8.nr - b'i', - ))), namedtuple('SmallBHead8', ('code', 'len', 'old', 'SDNAnr', 'nr')) - else: - return struct.Struct(b''.join(( - self.endian_str, - # LargeBHead8.code - b'4s', - # LargeBHead8.SDNAnr - b'i', - # LargeBHead8.old - b'Q', - # LargeBHead8.len - b'q', - # LargeBHead8.nr - b'q', - ))), namedtuple('LargeBHead8', ('code', 'SDNAnr', 'old', 'len', 'nr')) - class DNAName: """