Core: extract blendfile_header.py as common utility for parsing .blend files

This new file can parse the file header (first few bytes) as well as the block
headers.

Right now, this is used by two places:
* `blendfile.py` which is used by `blend2json.py`
* `blend_render_info.py`

This new module is shipped with Blender because it's needed for
`blend_render_info.py` which is shipped with Blender too. This makes using it in
`blendfile.py` (which is not shipped with Blender) a bit more annoying. However,
this is already not ideal, because e.g. `blend2json` also has to add to
`sys.path` already to be able to import `blendfile.py`.

This new file could also be used by blender-asset-tracer (BAT).

The new `BlendFileHeader` and `BlockHeader` types may be subclassed by code
using it, because it wants to store additional derived data (`blendfile.py` and
BAT need this).

New tests have been added that check that the file and block header is parsed
correctly for different kinds of .blend files.

Pull Request: https://projects.blender.org/blender/blender/pulls/140341
This commit is contained in:
Jacques Lucke
2025-06-23 12:53:55 +02:00
parent a5399af388
commit f0c7e52ff2
10 changed files with 468 additions and 220 deletions

View File

@@ -4,20 +4,14 @@
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later
# This module can get render info without running from inside blender. # 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__ = ( __all__ = (
"read_blend_rend_chunk", "read_blend_rend_chunk",
) )
import blendfile_header
class RawBlendFileReader: class RawBlendFileReader:
""" """
@@ -64,75 +58,51 @@ class RawBlendFileReader:
return False 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): def _read_blend_rend_chunk_from_file(blendfile, filepath):
import struct import struct
import sys import sys
from os import SEEK_CUR from os import SEEK_CUR
head = blendfile.read(7) try:
if head != b'BLENDER': blender_header = blendfile_header.BlendFileHeader(blendfile)
except blendfile_header.BlendHeaderError:
sys.stderr.write("Not a blend file: {:s}\n".format(filepath)) sys.stderr.write("Not a blend file: {:s}\n".format(filepath))
return [] 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 = [] 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. block_header_struct = blender_header.create_block_header_struct()
while (bhead_id := blendfile.read(4)) != b'ENDB': while bhead := blendfile_header.BlockHeader(blendfile, block_header_struct):
if bhead.code == b'ENDB':
if len(bhead_id) != 4:
sys.stderr.write("Unable to read until ENDB block (corrupt file): {:s}\n".format(filepath))
break break
remaining_bytes = bhead.size
sizeof_data_left = struct.unpack('>i' if is_big_endian else '<i', blendfile.read(4))[0] if bhead.code == b'REND':
if sizeof_data_left < 0: rend_block_struct = get_render_info_structure(endian_str, bhead.size)
# Very unlikely, but prevent other errors. start_frame, end_frame, scene_name = rend_block_struct.unpack(blendfile.read(rend_block_struct.size))
sys.stderr.write("Negative block size found (corrupt file): {:s}\n".format(filepath)) remaining_bytes -= rend_block_struct.size
break
# 4 from the `head_id`, another 4 for the size of the BHEAD.
sizeof_bhead_left = sizeof_bhead - 8
# The remainder of the BHEAD struct is not used.
blendfile.seek(sizeof_bhead_left, SEEK_CUR)
if bhead_id == b'REND':
# Now we want the scene name, start and end frame. this is 32bits long.
start_frame, end_frame = struct.unpack('>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'
scene_name = scene_name[:scene_name.index(b'\0')] scene_name = scene_name[:scene_name.index(b'\0')]
# It's possible old blend files are not UTF8 compliant, use `surrogateescape`. # It's possible old blend files are not UTF8 compliant, use `surrogateescape`.
scene_name = scene_name.decode("utf8", errors="surrogateescape") scene_name = scene_name.decode("utf8", errors="surrogateescape")
scenes.append((start_frame, end_frame, scene_name)) scenes.append((start_frame, end_frame, scene_name))
if sizeof_data_left > 0: blendfile.seek(remaining_bytes, SEEK_CUR)
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
return scenes return scenes

View File

@@ -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

BIN
tests/files/io_tests/blend_parsing/BHead4.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tests/files/io_tests/blend_parsing/BHead4_big_endian.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tests/files/io_tests/blend_parsing/LargeBHead8.blend (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tests/files/io_tests/blend_parsing/SmallBHead8.blend (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -340,6 +340,12 @@ if(TEST_SRC_DIR_EXISTS)
--output-dir ${TEST_OUT_DIR}/blendfile_io/ --output-dir ${TEST_OUT_DIR}/blendfile_io/
--test-dir "${TEST_SRC_DIR}/libraries_and_linking" --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() endif()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -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()

View File

@@ -272,6 +272,12 @@ class TestBlendFileOpenLinkSaveAllTestFiles(TestHelper):
(OSError, RuntimeError), (OSError, RuntimeError),
"created by a Big Endian version of Blender, support for these files has been removed in Blender 5.0" "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) assert all(p.endswith("/") for p in self.excluded_open_link_dirs)

View File

@@ -18,11 +18,13 @@ __all__ = (
"BlendFileRaw", "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 gzip
import logging import logging
import os
import struct import struct
import tempfile import tempfile
import zstandard as zstd import zstandard as zstd
@@ -55,10 +57,8 @@ class BlendFile:
"filepath_orig", "filepath_orig",
# BlendFileHeader # BlendFileHeader
"header", "header",
# struct.Struct # blendfile_header.BlockHeaderStruct
"block_header_struct", "block_header_struct",
# namedtuple
"block_header_fields",
# BlendFileBlock # BlendFileBlock
"blocks", "blocks",
# [DNAStruct, ...] # [DNAStruct, ...]
@@ -80,7 +80,7 @@ class BlendFile:
log.debug("initializing reading blend-file") log.debug("initializing reading blend-file")
self.handle = handle self.handle = handle
self.header = BlendFileHeader(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.blocks = []
self.code_index = {} self.code_index = {}
self.structs = [] self.structs = []
@@ -245,18 +245,13 @@ class BlendFile:
return structs, sdna_index_from_id return structs, sdna_index_from_id
class BlendFileBlock: class BlendFileBlock(blendfile_header.BlockHeader):
""" """
Instance of a struct. Instance of a struct.
""" """
__slots__ = ( __slots__ = (
# BlendFile # BlendFile
"file", "file",
"code",
"size",
"addr_old",
"sdna_index",
"count",
"file_offset", "file_offset",
"user_data", "user_data",
) )
@@ -273,46 +268,10 @@ class BlendFileBlock:
)) ))
def __init__(self, handle, bfile): def __init__(self, handle, bfile):
super().__init__(handle, bfile.block_header_struct)
self.file = bfile self.file = bfile
self.user_data = None self.user_data = None
self.file_offset = handle.tell()
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
@property @property
def dna_type(self): def dna_type(self):
@@ -744,74 +703,12 @@ class BlendFileBlockRaw:
# Read Magic # Read Magic
class BlendFileHeader: class BlendFileHeader(blendfile_header.BlendFileHeader):
""" endian_index: int
BlendFileHeader allocates the first 12-17 bytes (depending on the file version) of a blend file endian_str: bytes
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",
)
def __init__(self, handle): def __init__(self, handle):
log.debug("reading blend-file-header") super().__init__(handle)
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)
if self.is_little_endian: if self.is_little_endian:
self.endian_str = b'<' self.endian_str = b'<'
@@ -820,51 +717,6 @@ class BlendFileHeader:
self.endian_index = 1 self.endian_index = 1
self.endian_str = b'>' 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: class DNAName:
""" """