Skip to content

Commit 2a6a7f8

Browse files
build json for long tail of headless blender tests
1 parent 236f549 commit 2a6a7f8

File tree

6 files changed

+1286
-8
lines changed

6 files changed

+1286
-8
lines changed

Justfile

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,25 @@ validate-zip:
77
# build the blender extension
88
build:
99
Blender --command extension build --source-dir extension/
10-
10+
# document the rust crate
1111
doc:
1212
cargo doc --all-features --no-deps --document-private-items
1313
clippy:
1414
cargo clippy
1515

16+
# copy-for-dev is a mac-specific filepath located to a directory
17+
# on my own (chris') filesystem. You'll want to change this path if
18+
# you're working on a different OS or with a different Blender version
19+
# requires a Blender restart.
20+
#
21+
# copies the python extension directly into the Blender addons directory.
1622
copy-for-dev:
17-
cp -r ./extension/* /Users/chris/Library/Application\ Support/Blender/4.4/extensions/user_default/bevy_skein/
23+
cp -r ./extension/* /Users/chris/Library/Application\ Support/Blender/4.4/extensions/user_default/bevy_skein/
24+
25+
# run python tests headlessly in a blender environment
26+
run-headless-blender-tests:
27+
nu ./tools/run-python-tests.nu
28+
29+
# combine all test-components/snapshots into one json file
30+
gather-snapshots:
31+
nu ./tools/gather-snapshots.nu

extension/fetch_bevy_type_registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class FetchBevyTypeRegistry(bpy.types.Operator):
1919
# execute is called to run the operator
2020
def execute(self, context):
2121
preferences = context.preferences
22-
debug = False
22+
debug = False
2323

2424
if 'unittest' not in sys.modules.keys():
2525
debug = preferences.addons[__package__].preferences.debug

extension/form_to_object.py

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,26 +60,127 @@ def get_data_from_active_editor(context, context_key):
6060
# properly indicating that a Vec3 has x,y,z fields. BUT the serialization
6161
# is overridden and actually needs to be an array of 3 values
6262
try:
63+
print("type_override", obj.type_override)
6364
match obj.type_override:
64-
case "glam::Vec2":
65+
case "glam::Vec2" | "glam::DVec2" | "glam::I8Vec2" | "glam::U8Vec2" | "glam::I16Vec2" | "glam::U16Vec2" | "glam::IVec2" | "glam::UVec2" | "glam::I64Vec2" | "glam::U64Vec2" | "glam::BVec2":
6566
return [
6667
getattr(obj, "x"),
6768
getattr(obj, "y"),
6869
]
69-
case "glam::Vec3":
70+
case "glam::Vec3" | "glam::Vec3A" | "glam::DVec3" | "glam::I8Vec3" | "glam::U8Vec3" | "glam::I16Vec3" | "glam::U16Vec3" | "glam::IVec3" | "glam::UVec3" | "glam::I64Vec3" | "glam::U64Vec3" | "glam::BVec3":
71+
return [
72+
getattr(obj, "x"),
73+
getattr(obj, "y"),
74+
getattr(obj, "z"),
75+
]
76+
case "glam::Vec4" | "glam::DVec4" | "glam::I8Vec4" | "glam::U8Vec4" | "glam::I16Vec4" | "glam::U16Vec4" | "glam::IVec4" | "glam::UVec4" | "glam::I64Vec4" | "glam::U64Vec4" | "glam::BVec4":
7077
return [
7178
getattr(obj, "x"),
7279
getattr(obj, "y"),
7380
getattr(obj, "z"),
81+
getattr(obj, "w"),
7482
]
75-
case "glam::Vec4":
83+
case "glam::Quat" | "glam::DQuat":
7684
return [
7785
getattr(obj, "x"),
7886
getattr(obj, "y"),
7987
getattr(obj, "z"),
8088
getattr(obj, "w"),
8189
]
90+
case "glam::Mat2" | "glam::DMat2":
91+
x_axis = getattr(obj, "x_axis")
92+
y_axis = getattr(obj, "y_axis")
93+
94+
return [
95+
getattr(x_axis, "x"),
96+
getattr(x_axis, "y"),
97+
98+
getattr(y_axis, "x"),
99+
getattr(y_axis, "y"),
100+
]
101+
102+
case "glam::Mat3" | "glam::Mat3A" | "glam::DMat3":
103+
x_axis = getattr(obj, "x_axis")
104+
y_axis = getattr(obj, "y_axis")
105+
z_axis = getattr(obj, "z_axis")
106+
107+
return [
108+
getattr(x_axis, "x"),
109+
getattr(x_axis, "y"),
110+
getattr(x_axis, "z"),
111+
112+
getattr(y_axis, "x"),
113+
getattr(y_axis, "y"),
114+
getattr(y_axis, "z"),
115+
116+
getattr(z_axis, "x"),
117+
getattr(z_axis, "y"),
118+
getattr(z_axis, "z"),
119+
]
120+
case "glam::Mat4" | "glam::DMat4":
121+
x_axis = getattr(obj, "x_axis")
122+
y_axis = getattr(obj, "y_axis")
123+
z_axis = getattr(obj, "z_axis")
124+
w_axis = getattr(obj, "w_axis")
125+
126+
return [
127+
getattr(x_axis, "x"),
128+
getattr(x_axis, "y"),
129+
getattr(x_axis, "z"),
130+
getattr(x_axis, "w"),
131+
132+
getattr(y_axis, "x"),
133+
getattr(y_axis, "y"),
134+
getattr(y_axis, "z"),
135+
getattr(y_axis, "w"),
136+
137+
getattr(z_axis, "x"),
138+
getattr(z_axis, "y"),
139+
getattr(z_axis, "z"),
140+
getattr(z_axis, "w"),
141+
142+
getattr(w_axis, "x"),
143+
getattr(w_axis, "y"),
144+
getattr(w_axis, "z"),
145+
getattr(w_axis, "w"),
146+
]
82147

148+
case "glam::Affine2" | "glam::DAffine2":
149+
mat = getattr(obj, "matrix2")
150+
x_axis = getattr(mat, "x_axis")
151+
y_axis = getattr(mat, "y_axis")
152+
translation = getattr(obj, "translation")
153+
154+
return [
155+
getattr(x_axis, "x"),
156+
getattr(x_axis, "y"),
157+
getattr(y_axis, "x"),
158+
getattr(y_axis, "y"),
159+
getattr(translation, "x"),
160+
getattr(translation, "y"),
161+
]
162+
case "glam::Affine3A" | "glam::DAffine3":
163+
mat = getattr(obj, "matrix3")
164+
x_axis = getattr(mat, "x_axis")
165+
y_axis = getattr(mat, "y_axis")
166+
z_axis = getattr(mat, "z_axis")
167+
translation = getattr(obj, "translation")
168+
169+
return [
170+
getattr(x_axis, "x"),
171+
getattr(x_axis, "y"),
172+
getattr(x_axis, "z"),
173+
getattr(y_axis, "x"),
174+
getattr(y_axis, "y"),
175+
getattr(y_axis, "z"),
176+
getattr(z_axis, "x"),
177+
getattr(z_axis, "y"),
178+
getattr(z_axis, "z"),
179+
getattr(translation, "x"),
180+
getattr(translation, "y"),
181+
getattr(translation, "z"),
182+
]
183+
83184
except AttributeError:
84185
# Not all PropertyGroups have the type_override attribute, so
85186
# this is a common failure case that doesn't actually mean failure

tools/blender_python_embedded_tests.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import inspect
12
import json
23
import unittest
34
import bpy
@@ -13,7 +14,11 @@
1314
from extension.fetch_bevy_type_registry import process_registry
1415
from extension.form_to_object import get_data_from_active_editor
1516

16-
class SimplePropertyTest(unittest.TestCase):
17+
snapshots = {}
18+
with open("tools/combined_snapshots.json") as snapshots_file:
19+
snapshots = json.loads(snapshots_file.read())
20+
21+
class ComponentPropertyTests(unittest.TestCase):
1722
def test_linear_velocity(self):
1823

1924
bpy.context.window_manager.selected_component = "test_components::LinearVelocity";
@@ -31,7 +36,64 @@ def test_linear_velocity(self):
3136
container.selected_type_path
3237
)
3338

34-
self.assertEqual(data, [2.0, 10.0, 0.0])
39+
self.assertEqual(data, [2.0, 0.0, 0.0])
40+
bpy.ops.bevy.remove_bevy_component()
41+
42+
def test_snapshots(self):
43+
self.maxDiff = None
44+
for snapshot in snapshots:
45+
46+
with self.subTest(snapshot):
47+
# assert test.expected == make_relative(path=test.path, root=test.root)
48+
# each snapshot is a single key/value pair
49+
for key, value in snapshot.items():
50+
51+
bpy.context.window_manager.selected_component = key;
52+
bpy.ops.bevy.insert_bevy_component()
53+
54+
container = bpy.context.active_object.skein_two[0]
55+
56+
try:
57+
if inspect.isclass(bpy.context.window_manager.skein_property_groups[container.selected_type_path]):
58+
# load-bearing getattrs
59+
# without this, the PointerProperty values
60+
# don't actually get initialized
61+
touch_all_fields(container, key)
62+
63+
data = get_data_from_active_editor(
64+
container,
65+
container.selected_type_path
66+
)
67+
self.assertEqual(data, value)
68+
else:
69+
data = getattr(container, container.selected_type_path)
70+
self.assertEqual(data, value)
71+
except Exception as e:
72+
raise
73+
finally:
74+
# always remove the bevy component,
75+
# we're working in a headless blender
76+
# context so we have to clean up shared
77+
# resources ourselves
78+
bpy.ops.bevy.remove_bevy_component()
79+
80+
# getattr on anything that is a PointerProperty
81+
#
82+
# blender requires us to touch all fields
83+
# to be able to read the values, otherwise they
84+
# won't be initialized. This usually happens
85+
# when displaying them in the UI with
86+
# layout.prop/render_props but we need to hack it
87+
# manually here for tests
88+
def touch_all_fields(context, key):
89+
try:
90+
obj = getattr(context, key)
91+
annotations = getattr(obj, "__annotations__")
92+
for key, value in annotations.items():
93+
if "PointerProperty" == value.function.__name__:
94+
touch_all_fields(obj, key)
95+
except:
96+
pass
3597

3698
if __name__ == '__main__':
3799
import sys

0 commit comments

Comments
 (0)