PyAPI: support multi-dimensional arrays for bpy.props vector types
- Multi-dimensional boolean, int and float vector types are supported. - A sequence of int's for the "size" is used to declare dimensions. - Nested sequences are required for default arguments. Now it's possible to define matrix properties, for e.g: bpy.props.FloatVectorProperty(size=(4, 4), subtype='MATRIX')
This commit is contained in:
@@ -2,18 +2,61 @@
|
||||
|
||||
# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_prop_array.py -- --verbose
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolVectorProperty,
|
||||
FloatVectorProperty,
|
||||
IntVectorProperty,
|
||||
)
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
id_inst = bpy.context.scene
|
||||
id_type = bpy.types.Scene
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utility Functions
|
||||
|
||||
def seq_items_xform(data, xform_fn):
|
||||
"""
|
||||
Recursively expand items using `xform_fn`.
|
||||
"""
|
||||
if hasattr(data, "__len__"):
|
||||
return tuple(seq_items_xform(v, xform_fn) for v in data)
|
||||
return xform_fn(data)
|
||||
|
||||
|
||||
def seq_items_as_tuple(data):
|
||||
"""
|
||||
Return nested sequences as a nested tuple.
|
||||
Useful when comparing different kinds of nested sequences.
|
||||
"""
|
||||
return seq_items_xform(data, lambda v: v)
|
||||
|
||||
|
||||
def seq_items_as_dims(data):
|
||||
"""
|
||||
Nested length calculation, extracting the length from each sequence.
|
||||
Where a 4x4 matrix returns ``(4, 4)`` for example.
|
||||
"""
|
||||
return ((len(data),) + seq_items_as_dims(data[0])) if hasattr(data, "__len__") else ()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tests
|
||||
|
||||
class TestPropArray(unittest.TestCase):
|
||||
def setUp(self):
|
||||
bpy.types.Scene.test_array_f = bpy.props.FloatVectorProperty(size=10)
|
||||
bpy.types.Scene.test_array_i = bpy.props.IntVectorProperty(size=10)
|
||||
id_type.test_array_f = FloatVectorProperty(size=10)
|
||||
id_type.test_array_i = IntVectorProperty(size=10)
|
||||
scene = bpy.context.scene
|
||||
self.array_f = scene.test_array_f
|
||||
self.array_i = scene.test_array_i
|
||||
|
||||
def tearDown(self):
|
||||
del id_type.test_array_f
|
||||
del id_type.test_array_i
|
||||
|
||||
def test_foreach_getset_i(self):
|
||||
with self.assertRaises(TypeError):
|
||||
self.array_i.foreach_set(range(5))
|
||||
@@ -79,6 +122,79 @@ class TestPropArray(unittest.TestCase):
|
||||
self.assertEqual(v1, v2)
|
||||
|
||||
|
||||
class TestPropArrayMultiDimensional(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self._initial_dir = set(dir(id_type))
|
||||
|
||||
def tearDown(self):
|
||||
for member in (set(dir(id_type)) - self._initial_dir):
|
||||
delattr(id_type, member)
|
||||
|
||||
def test_defaults(self):
|
||||
# The data is in int format, converted into float & bool to avoid duplication.
|
||||
default_data = (
|
||||
# 1D.
|
||||
(1,),
|
||||
(1, 2),
|
||||
(1, 2, 3),
|
||||
(1, 2, 3, 4),
|
||||
# 2D.
|
||||
((1,),),
|
||||
((1,), (11,)),
|
||||
((1, 2), (11, 22)),
|
||||
((1, 2, 3), (11, 22, 33)),
|
||||
((1, 2, 3, 4), (11, 22, 33, 44)),
|
||||
# 3D.
|
||||
(((1,),),),
|
||||
((1,), (11,), (111,)),
|
||||
((1, 2), (11, 22), (111, 222),),
|
||||
((1, 2, 3), (11, 22, 33), (111, 222, 333)),
|
||||
((1, 2, 3, 4), (11, 22, 33, 44), (111, 222, 333, 444)),
|
||||
)
|
||||
for data in default_data:
|
||||
for (vector_prop_fn, xform_fn) in (
|
||||
(BoolVectorProperty, lambda v: bool(v % 2)),
|
||||
(FloatVectorProperty, lambda v: float(v)),
|
||||
(IntVectorProperty, lambda v: v),
|
||||
):
|
||||
data_native = seq_items_xform(data, xform_fn)
|
||||
size = seq_items_as_dims(data)
|
||||
id_type.temp = vector_prop_fn(size=size, default=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(self):
|
||||
data = ((1, 2, 3, 4), (11, 22, 33, 44), (111, 222, 333, 444), (1111, 2222, 3333, 4444),)
|
||||
data_native = seq_items_xform(data, lambda v: float(v))
|
||||
id_type.temp = FloatVectorProperty(size=(4, 4), subtype='MATRIX', default=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_with_callbacks(self):
|
||||
# """
|
||||
# Internally matrices have rows/columns swapped,
|
||||
# This test ensures this is being done properly.
|
||||
# """
|
||||
data = ((1, 2, 3, 4), (11, 22, 33, 44), (111, 222, 333, 444), (1111, 2222, 3333, 4444),)
|
||||
data_native = seq_items_xform(data, lambda v: float(v))
|
||||
local_data = {"array": data}
|
||||
|
||||
def get_fn(id_arg):
|
||||
return local_data["array"]
|
||||
|
||||
def set_fn(id_arg, value):
|
||||
local_data["array"] = value
|
||||
|
||||
id_type.temp = FloatVectorProperty(size=(4, 4), 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
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
|
||||
|
||||
Reference in New Issue
Block a user