BPY: Implement get_transform and set_transform for runtime-defined RNA properties.

Improve handling of runtime defined python RNA properties. Mainly:
* Add `get_transform` and `set_transform` new callbacks.
  These allow to edit the value, while still using the default
  (IDProperty-based) storage system.
* Read-only properties should now be defined using a new `options` flag,
  `READ_ONLY`.
* `get`/`set` should only be used when storing data outside of the
  default system now.
  * Having a `get` without a `set` defined forces property to be
    read-only (same behavior as before).
  * Having a `set` without a `get` is now an error.
* Just like with existing `get/set` callbacks, `get_/set_transform`
  callbacks must always generate values matching the constraints defined
  by their `bpy.props` property definition (same type, within required
  range, same dimensions/sizes for the `Vector` properties, etc.).
* To simplify handling of non-statically sized strings, the relevant
  RNA API has been modified, to use `std::string` instead of
  (allocated) char arrays.

Relevant unittests and benchmarking have been added or updated as part
of this project.

Note: From initial benchmarking, 'transform' versions of get/set are
several times faster than 'real' get/set.

Implements #141042.

Pull Request: https://projects.blender.org/blender/blender/pulls/141303
This commit is contained in:
Bastien Montagne
2025-09-02 11:30:09 +02:00
committed by Bastien Montagne
parent 75c66158aa
commit 469f54f484
19 changed files with 2762 additions and 740 deletions

View File

@@ -147,32 +147,71 @@ class TestPropArrayIndex(unittest.TestCase):
self.test_array_b_2d_storage = [[bool(v) for v in range(self.size_2d[1])] for i in range(self.size_2d[0])]
def set_(s, v):
def bool_set_(s, v):
self.test_array_b_2d_storage = v
id_type.test_array_b_2d_getset = BoolVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_b_2d_storage,
set=set_,
)
self.test_array_i_2d_storage = [[int(v) for v in range(self.size_2d[1])] for i in range(self.size_2d[0])]
def set_(s, v):
def int_set_(s, v):
self.test_array_i_2d_storage = v
id_type.test_array_i_2d_getset = IntVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_i_2d_storage,
set=set_,
)
self.test_array_f_2d_storage = [[float(v) for v in range(self.size_2d[1])] for i in range(self.size_2d[0])]
def set_(s, v):
def float_set_(s, v):
self.test_array_f_2d_storage = v
id_type.test_array_b_2d_getset = BoolVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_b_2d_storage,
set=bool_set_,
)
id_type.test_array_i_2d_getset = IntVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_i_2d_storage,
set=int_set_,
)
id_type.test_array_f_2d_getset = FloatVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_f_2d_storage,
set=set_,
set=float_set_,
)
id_type.test_array_b_3d_transform = BoolVectorProperty(
size=self.size_3d,
get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: not v),
set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: not v),
)
id_type.test_array_i_3d_transform = IntVectorProperty(
size=self.size_3d,
get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v + 1),
set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v - 1),
)
id_type.test_array_f_3d_transform = FloatVectorProperty(
size=self.size_3d,
get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v * 2.0),
set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v / 2.0),
)
id_type.test_array_b_2d_getset_transform = BoolVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_b_2d_storage,
set=bool_set_,
get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: not v),
set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: not v),
)
id_type.test_array_i_2d_getset_transform = IntVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_i_2d_storage,
set=int_set_,
get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v + 1),
set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v - 1),
)
id_type.test_array_f_2d_getset_transform = FloatVectorProperty(
size=self.size_2d,
get=lambda s: self.test_array_f_2d_storage,
set=float_set_,
get_transform=lambda s, c_v, isset: seq_items_xform(c_v, lambda v: v * 2.0),
set_transform=lambda s, n_v, c_v, isset: seq_items_xform(n_v, lambda v: v / 2.0),
)
def tearDown(self):
@@ -190,6 +229,14 @@ class TestPropArrayIndex(unittest.TestCase):
del id_type.test_array_i_2d_getset
del id_type.test_array_b_2d_getset
del id_type.test_array_f_3d_transform
del id_type.test_array_i_3d_transform
del id_type.test_array_b_3d_transform
del id_type.test_array_f_2d_getset_transform
del id_type.test_array_i_2d_getset_transform
del id_type.test_array_b_2d_getset_transform
@staticmethod
def compute_slice_len(s):
if not isinstance(s, slice):
@@ -297,6 +344,36 @@ class TestPropArrayIndex(unittest.TestCase):
id_inst.test_array_f_2d_getset, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d
)
def test_indices_access_b_3d_transform(self):
self.do_test_indices_access(
id_inst.test_array_b_3d_transform, self.size_3d, self.valid_indices_3d, self.invalid_indices_3d
)
def test_indices_access_i_3d_transform(self):
self.do_test_indices_access(
id_inst.test_array_i_3d_transform, self.size_3d, self.valid_indices_3d, self.invalid_indices_3d
)
def test_indices_access_f_3d_transform(self):
self.do_test_indices_access(
id_inst.test_array_f_3d_transform, self.size_3d, self.valid_indices_3d, self.invalid_indices_3d
)
def test_indices_access_b_2d_getset_transform(self):
self.do_test_indices_access(
id_inst.test_array_b_2d_getset_transform, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d
)
def test_indices_access_i_2d_getset_transform(self):
self.do_test_indices_access(
id_inst.test_array_i_2d_getset_transform, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d
)
def test_indices_access_f_2d_getset_transform(self):
self.do_test_indices_access(
id_inst.test_array_f_2d_getset_transform, self.size_2d, self.valid_indices_2d, self.invalid_indices_2d
)
class TestPropArrayForeach(unittest.TestCase):
# Test foreach_get/_set access of Int and Float vector properties (bool ones do not support this).
@@ -499,12 +576,37 @@ class TestPropArrayMultiDimensional(unittest.TestCase):
def set_fn(id_arg, value):
local_data["array"] = value
def get_tx_fn(id_arg, curr_value, is_set):
return seq_items_xform(curr_value, lambda v: v + 1.0)
def set_tx_fn(id_arg, new_value, curr_value, is_set):
return seq_items_xform(new_value, lambda v: v - 1.0)
id_type.temp = FloatVectorProperty(size=(dim_x, dim_y), subtype='MATRIX', get=get_fn, set=set_fn)
id_inst.temp = data_native
data_as_tuple = seq_items_as_tuple(id_inst.temp)
self.assertEqual(data_as_tuple, data_native)
del id_type.temp
id_type.temp = FloatVectorProperty(
size=(dim_x, dim_y), subtype='MATRIX', get_transform=get_tx_fn, set_transform=set_tx_fn)
id_inst.temp = data_native
data_as_tuple = seq_items_as_tuple(id_inst.temp)
self.assertEqual(data_as_tuple, data_native)
del id_type.temp
id_type.temp = FloatVectorProperty(
size=(dim_x, dim_y),
subtype='MATRIX',
get=get_fn,
set=set_fn,
get_transform=get_tx_fn,
set_transform=set_tx_fn)
id_inst.temp = data_native
data_as_tuple = seq_items_as_tuple(id_inst.temp)
self.assertEqual(data_as_tuple, data_native)
del id_type.temp
def test_matrix_3x3(self):
self._test_matrix(3, 3)