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

@@ -7,6 +7,8 @@ import bpy
import rna_prop_ui
import idprop
import io
import sys
import unittest
from array import array
@@ -266,6 +268,12 @@ class TestIdPropertyDynamicRNA(TestHelper, unittest.TestCase):
string_prop: bpy.props.StringProperty()
int_prop: bpy.props.IntProperty()
float_array_prop: bpy.props.FloatVectorProperty(size=[3])
# Basic get/set transform.
string_prop_transform: bpy.props.StringProperty(
default="test",
maxlen=10,
get_transform=lambda self, storage_val, is_set: storage_val + "!!",
set_transform=lambda self, new_val, storage_val, is_set: storage_val + "!!" + new_val)
def setUp(self):
super().setUp()
@@ -348,6 +356,40 @@ class TestIdPropertyDynamicRNA(TestHelper, unittest.TestCase):
with self.assertRaises(TypeError):
self.id.bl_system_properties_get()['dynrna_prop']['float_array_prop'] = [1.0, 10.0, 100.0, 0.1]
def test_get_set_transform(self):
self.assertEqual(len(self.id.dynrna_prop.string_prop_transform), 6)
self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!")
# Default value only, was not yet set.
self.assertFalse('string_prop_transform' in self.id.bl_system_properties_get()['dynrna_prop'])
self.id.dynrna_prop.string_prop_transform = "-"
self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!-!!")
self.assertEqual(self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'], "test!!-")
# Raw-set exactly maxlen - 1 char.
self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'] = "test!!tes"
# get_transform will produce an 11-char results, which should trigger an error.
# These asserts are not raised currently, but only print in `stderr`...
# But the returned string is the 'storage' one, not the result from get_transform.
stderr, sys.stderr = sys.stderr, io.StringIO()
self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!tes")
self.assertTrue("ValueError" in sys.stderr.getvalue() and
"10" in sys.stderr.getvalue() and "11" in sys.stderr.getvalue())
sys.stderr.close()
sys.stderr = stderr
# Raw-set back to default value.
self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'] = "test"
# Now set_transform will return 12-char string, wich is also invalid and discarded.
stderr, sys.stderr = sys.stderr, io.StringIO()
self.id.dynrna_prop.string_prop_transform = "test!!"
self.assertTrue("ValueError" in sys.stderr.getvalue() and
"10" in sys.stderr.getvalue() and "12" in sys.stderr.getvalue())
sys.stderr.close()
sys.stderr = stderr
self.assertEqual(self.id.bl_system_properties_get()['dynrna_prop']['string_prop_transform'], "test")
self.assertEqual(self.id.dynrna_prop.string_prop_transform, "test!!")
class TestIdPropertyGroupView(TestHelper, unittest.TestCase):