Add Chromium-only Blender WebEngine parity work
This commit is contained in:
234
blender-5.2.0/scripts/modules/_blendfile_header.py
Normal file
234
blender-5.2.0/scripts/modules/_blendfile_header.py
Normal 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}".format(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}".format(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}".format(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}".format(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 {:d}".format(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
|
||||
Reference in New Issue
Block a user