From 47c3e8d77fad6522aa29682f47f7acc033405442 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 24 Nov 2025 15:07:46 +0100 Subject: [PATCH] Struct.field_set - support setting subproperties (#92899) Extend `Struct.field_set()` so that the `path` type can be a `dna.FieldPath`, to mirror `Struct.field_get`'s `path` type, and allow users to set file-block's subproperties. For example this allows setting `object_block[(b'id', 'name')]`. Also, along the way, added a test for getting subproperty value. Reviewed-on: https://projects.blender.org/blender/blender-asset-tracer/pulls/92899 --- blender_asset_tracer/blendfile/__init__.py | 4 ++-- blender_asset_tracer/blendfile/dna.py | 3 +-- tests/test_blendfile_dna.py | 26 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/blender_asset_tracer/blendfile/__init__.py b/blender_asset_tracer/blendfile/__init__.py index 8a2e1b1..a109f3f 100644 --- a/blender_asset_tracer/blendfile/__init__.py +++ b/blender_asset_tracer/blendfile/__init__.py @@ -726,7 +726,7 @@ class BlendFileBlock: hsh = zlib.adler32(str(value).encode(), hsh) return hsh - def set(self, path: bytes, value): + def set(self, path: dna.FieldPath, value): dna_struct = self.bfile.structs[self.sdna_index] self.bfile.mark_modified() self.bfile.fileobj.seek(self.file_offset, os.SEEK_SET) @@ -835,7 +835,7 @@ class BlendFileBlock: def __getitem__(self, path: dna.FieldPath): return self.get(path) - def __setitem__(self, item: bytes, value) -> None: + def __setitem__(self, item: dna.FieldPath, value) -> None: self.set(item, value) def has_field(self, name: bytes) -> bool: diff --git a/blender_asset_tracer/blendfile/dna.py b/blender_asset_tracer/blendfile/dna.py index e4a536e..814d91a 100644 --- a/blender_asset_tracer/blendfile/dna.py +++ b/blender_asset_tracer/blendfile/dna.py @@ -310,7 +310,7 @@ class Struct: self, file_header: header.BlendFileHeader, fileobj: typing.IO[bytes], - path: bytes, + path: FieldPath, value: typing.Any, ): """Write a value to the blend file. @@ -319,7 +319,6 @@ class Struct: struct on disk (e.g. the start of the BlendFileBlock containing the data). """ - assert isinstance(path, bytes), "path should be bytes, but is %s" % type(path) field, offset = self.field_from_path(file_header.pointer_size, path) diff --git a/tests/test_blendfile_dna.py b/tests/test_blendfile_dna.py index 31a3719..946aecd 100644 --- a/tests/test_blendfile_dna.py +++ b/tests/test_blendfile_dna.py @@ -93,6 +93,10 @@ class StructTest(unittest.TestCase): self.s_ulong = dna.Struct(b"ulong", 8) self.s_uint64 = dna.Struct(b"uint64_t", 8) self.s_uint128 = dna.Struct(b"uint128_t", 16) # non-supported type + self.s_substruct = dna.Struct(b"substruct") + + self.f_substruct_name = dna.Field(self.s_char, dna.Name(b"name[10]"), 10, 0) + self.s_substruct.append_field(self.f_substruct_name) self.f_next = dna.Field(self.s, dna.Name(b"*next"), 8, 0) self.f_prev = dna.Field(self.s, dna.Name(b"*prev"), 8, 8) @@ -109,6 +113,7 @@ class StructTest(unittest.TestCase): self.f_testint = dna.Field(self.s_int, dna.Name(b"testint"), 4, 4178) self.f_testfloat = dna.Field(self.s_float, dna.Name(b"testfloat"), 4, 4182) self.f_testulong = dna.Field(self.s_ulong, dna.Name(b"testulong"), 8, 4186) + self.f_substruct = dna.Field(self.s_substruct, dna.Name(b"testsubstruct"), 10, 4194) self.s.append_field(self.f_next) self.s.append_field(self.f_prev) @@ -125,6 +130,7 @@ class StructTest(unittest.TestCase): self.s.append_field(self.f_testint) self.s.append_field(self.f_testfloat) self.s.append_field(self.f_testulong) + self.s.append_field(self.f_substruct) def test_autosize(self): with self.assertRaises(ValueError): @@ -263,6 +269,19 @@ class StructTest(unittest.TestCase): self.assertAlmostEqual(2.79, val[1]) fileobj.seek.assert_called_with(4144, os.SEEK_CUR) + def test_struct_field_get_subproperty(self): + fileobj = mock.MagicMock(io.BufferedReader) + fileobj.read.return_value = b"my_name" + + _, val = self.s.field_get( + self.FakeHeader(), + fileobj, + (b"testsubstruct", b"name"), + as_str=True, + ) + self.assertEqual("my_name", val) + fileobj.seek.assert_called_with(4194, os.SEEK_CUR) + def test_char_field_set(self): fileobj = mock.MagicMock(io.BufferedReader) value = 255 @@ -344,3 +363,10 @@ class StructTest(unittest.TestCase): expected = struct.pack(b">f", value) self.s.field_set(self.FakeHeader(), fileobj, b"testfloat", value) fileobj.write.assert_called_with(expected) + + def test_struct_field_set_subproperty(self): + fileobj = mock.MagicMock(io.BufferedReader) + expected = b"new_name\x00" + + self.s.field_set(self.FakeHeader(), fileobj, (b"testsubstruct", b"name"), "new_name") + fileobj.write.assert_called_with(expected)