1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
25 from bpy.types import UIList
29 lib_path = os.path.join(tempfile.gettempdir(), "lib.blend")
30 test_path = os.path.join(tempfile.gettempdir(), "test.blend")
33 def print_fail_msg_and_exit(msg):
38 return sys.exc_info()[2].tb_frame.f_back.f_back.f_back.f_lineno
41 return inspect.currentframe().f_code.co_filename
43 print("'%s': %d >> %s" % (__FILE__(), __LINE__(), msg), file=sys.stderr)
49 def abort_if_false(expr, msg=None):
53 print_fail_msg_and_exit(msg)
56 class TestClass(bpy.types.PropertyGroup):
57 test_prop = bpy.props.PointerProperty(type=bpy.types.Object)
58 name = bpy.props.StringProperty()
61 def get_scene(lib_name, sce_name):
62 for s in bpy.data.scenes:
63 if s.name == sce_name:
64 if (s.library and s.library.name == lib_name) or \
65 (lib_name == None and s.library == None):
69 def check_crash(fnc, args=None):
71 fnc(args) if args else fnc()
74 print_fail_msg_and_exit("test failed")
78 bpy.utils.register_class(TestClass)
79 bpy.types.Object.prop_array = bpy.props.CollectionProperty(
82 bpy.types.Object.prop = bpy.props.PointerProperty(type=bpy.types.Object)
86 bpy.ops.wm.read_factory_settings()
88 # datablock pointer to the Camera object
89 bpy.data.objects["Cube"].prop = bpy.data.objects['Camera']
91 # array of datablock pointers to the Lamp object
92 for i in range(0, arr_len):
93 a = bpy.data.objects["Cube"].prop_array.add()
94 a.test_prop = bpy.data.objects['Lamp']
95 a.name = a.test_prop.name
97 # make unique named copy of the cube
98 ob = bpy.data.objects["Cube"].copy()
99 bpy.context.scene.objects.link(ob)
101 bpy.data.objects["Cube.001"].name = "Unique_Cube"
103 # duplicating of Cube
104 for i in range(0, ob_cp_count):
105 ob = bpy.data.objects["Cube"].copy()
106 bpy.context.scene.objects.link(ob)
109 bpy.data.scenes["Scene"].use_nodes = True
110 bpy.data.scenes["Scene"].node_tree.nodes['Render Layers']["prop"] =\
111 bpy.data.objects['Camera']
113 # rename scene and save
114 bpy.data.scenes["Scene"].name = "Scene_lib"
115 bpy.ops.wm.save_as_mainfile(filepath=lib_path)
120 abort_if_false(bpy.data.objects["Cube"].prop == bpy.data.objects['Camera'])
122 # check array of pointers in duplicated object
123 for i in range(0, arr_len):
124 abort_if_false(bpy.data.objects["Cube.001"].prop_array[i].test_prop ==
125 bpy.data.objects['Lamp'])
128 def check_lib_linking():
130 bpy.ops.wm.read_factory_settings()
132 # link scene to the startup file
133 with bpy.data.libraries.load(lib_path, link=True) as (data_from, data_to):
134 data_to.scenes = ["Scene_lib"]
136 o = bpy.data.scenes["Scene_lib"].objects['Unique_Cube']
138 abort_if_false(o.prop_array[0].test_prop == bpy.data.scenes["Scene_lib"].objects['Lamp'])
139 abort_if_false(o.prop == bpy.data.scenes["Scene_lib"].objects['Camera'])
140 abort_if_false(o.prop.library == o.library)
142 bpy.ops.wm.save_as_mainfile(filepath=test_path)
145 def check_linked_scene_copying():
146 # full copy of the scene with datablock props
147 bpy.ops.wm.open_mainfile(filepath=test_path)
148 bpy.data.screens['Default'].scene = bpy.data.scenes["Scene_lib"]
149 bpy.ops.scene.new(type='FULL_COPY')
152 bpy.ops.wm.save_as_mainfile(filepath=test_path)
153 bpy.ops.wm.open_mainfile(filepath=test_path)
155 intern_sce = get_scene(None, "Scene_lib")
156 extern_sce = get_scene("Lib", "Scene_lib")
159 # we made full copy from linked scene, so pointers must equal each other
160 abort_if_false(intern_sce.node_tree.nodes['Render Layers']["prop"] and
161 intern_sce.node_tree.nodes['Render Layers']["prop"] ==
162 extern_sce.node_tree.nodes['Render Layers']["prop"])
165 def check_scene_copying():
166 # full copy of the scene with datablock props
167 bpy.ops.wm.open_mainfile(filepath=lib_path)
168 bpy.data.screens['Default'].scene = bpy.data.scenes["Scene_lib"]
169 bpy.ops.scene.new(type='FULL_COPY')
171 path = test_path + "_"
173 bpy.ops.wm.save_as_mainfile(filepath=path)
174 bpy.ops.wm.open_mainfile(filepath=path)
176 first_sce = get_scene(None, "Scene_lib")
177 second_sce = get_scene(None, "Scene_lib.001")
180 # must point to own scene camera
181 abort_if_false(not (first_sce.node_tree.nodes['Render Layers']["prop"] ==
182 second_sce.node_tree.nodes['Render Layers']["prop"]))
186 def test_users_counting():
187 bpy.ops.wm.read_factory_settings()
188 lamp_us = bpy.data.objects["Lamp"].data.users
190 for i in range(0, n):
191 bpy.data.objects["Cube"]["a%s" % i] = bpy.data.objects["Lamp"].data
192 abort_if_false(bpy.data.objects["Lamp"].data.users == lamp_us + n)
194 for i in range(0, int(n / 2)):
195 bpy.data.objects["Cube"]["a%s" % i] = 1
196 abort_if_false(bpy.data.objects["Lamp"].data.users == lamp_us + int(n / 2))
204 check_linked_scene_copying()
205 check_scene_copying()
208 # check restrictions for datablock pointers for some classes; GUI for manual testing
209 def test_restrictions1():
210 class TEST_Op(bpy.types.Operator):
211 bl_idname = 'scene.test_op'
213 bl_options = {"INTERNAL"}
214 str_prop = bpy.props.StringProperty(name="str_prop")
216 # disallow registration of datablock properties in operators
217 # will be checked in the draw method (test manually)
219 # ValueError: bpy_struct "SCENE_OT_test_op" doesn't support datablock properties
220 id_prop = bpy.props.PointerProperty(type=bpy.types.Object)
222 def execute(self, context):
225 # just panel for testing the poll callback with lots of objects
226 class TEST_PT_DatablockProp(bpy.types.Panel):
227 bl_label = "Datablock IDProp"
228 bl_space_type = "PROPERTIES"
229 bl_region_type = "WINDOW"
230 bl_context = "render"
232 def draw(self, context):
233 self.layout.prop_search(context.scene, "prop", bpy.data,
235 self.layout.template_ID(context.scene, "prop1")
236 self.layout.prop_search(context.scene, "prop2", bpy.data, "node_groups")
238 op = self.layout.operator("scene.test_op")
239 op.str_prop = "test string"
242 op["ob"] = bpy.data.objects['Unique_Cube']
243 check_crash(test_fnc, op)
244 abort_if_false(not hasattr(op, "id_prop"))
246 bpy.utils.register_class(TEST_PT_DatablockProp)
247 bpy.utils.register_class(TEST_Op)
249 def poll(self, value):
250 return value.name in bpy.data.scenes["Scene_lib"].objects
252 def poll1(self, value):
255 bpy.types.Scene.prop = bpy.props.PointerProperty(type=bpy.types.Object)
256 bpy.types.Scene.prop1 = bpy.props.PointerProperty(type=bpy.types.Object, poll=poll)
257 bpy.types.Scene.prop2 = bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll1)
259 # check poll effect on UI (poll returns false => red alert)
260 bpy.context.scene.prop = bpy.data.objects["Lamp.001"]
261 bpy.context.scene.prop1 = bpy.data.objects["Lamp.001"]
263 # check incorrect type assignment
266 bpy.context.scene.prop2 = bpy.data.objects["Lamp.001"]
268 check_crash(sub_test)
270 bpy.context.scene.prop2 = bpy.data.node_groups.new("Shader", "ShaderNodeTree")
272 print("Please, test GUI performance manually on the Render tab, '%s' panel" %
273 TEST_PT_DatablockProp.bl_label, file=sys.stderr)
277 # check some possible regressions
278 def test_regressions():
279 bpy.types.Object.prop_str = bpy.props.StringProperty(name="str")
280 bpy.data.objects["Unique_Cube"].prop_str = "test"
282 bpy.types.Object.prop_gr = bpy.props.PointerProperty(
287 bpy.data.objects["Unique_Cube"].prop_gr = None
290 # test restrictions for datablock pointers
291 def test_restrictions2():
292 class TestClassCollection(bpy.types.PropertyGroup):
293 prop = bpy.props.CollectionProperty(
296 bpy.utils.register_class(TestClassCollection)
298 class TestPrefs(bpy.types.AddonPreferences):
299 bl_idname = "testprefs"
300 # expecting crash during registering
301 my_prop2 = bpy.props.PointerProperty(type=TestClass)
303 prop = bpy.props.PointerProperty(
305 type=TestClassCollection,
308 bpy.types.Addon.a = bpy.props.PointerProperty(type=bpy.types.Object)
310 class TestUIList(UIList):
311 test = bpy.props.PointerProperty(type=bpy.types.Object)
312 def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
313 layout.prop(item, "name", text="", emboss=False, icon_value=icon)
315 check_crash(bpy.utils.register_class, TestPrefs)
316 check_crash(bpy.utils.register_class, TestUIList)
318 bpy.utils.unregister_class(TestClassCollection)
323 test_users_counting()
326 check_crash(test_regressions)
330 if __name__ == "__main__":
336 traceback.print_exc()