GPencil: Fix unreported unable to deselect when masking is OFF
[blender.git] / tests / python / bl_pyapi_idprop_datablock.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 import bpy
20 import sys
21 import os
22 import tempfile
23 import traceback
24 import inspect
25 from bpy.types import UIList
26
27 arr_len = 100
28 ob_cp_count = 100
29 lib_path = os.path.join(tempfile.gettempdir(), "lib.blend")
30 test_path = os.path.join(tempfile.gettempdir(), "test.blend")
31
32
33 def print_fail_msg_and_exit(msg):
34     def __LINE__():
35         try:
36             raise Exception
37         except:
38             return sys.exc_info()[2].tb_frame.f_back.f_back.f_back.f_lineno
39
40     def __FILE__():
41         return inspect.currentframe().f_code.co_filename
42
43     print("'%s': %d >> %s" % (__FILE__(), __LINE__(), msg), file=sys.stderr)
44     sys.stderr.flush()
45     sys.stdout.flush()
46     os._exit(1)
47
48
49 def abort_if_false(expr, msg=None):
50     if not expr:
51         if not msg:
52             msg = "test failed"
53         print_fail_msg_and_exit(msg)
54
55
56 class TestClass(bpy.types.PropertyGroup):
57     test_prop = bpy.props.PointerProperty(type=bpy.types.Object)
58     name = bpy.props.StringProperty()
59
60
61 def get_scene(lib_name, sce_name):
62     for s in bpy.data.scenes:
63         if s.name == sce_name:
64             if (
65                     (s.library and s.library.name == lib_name) or
66                     (lib_name is None and s.library is None)
67             ):
68                 return s
69
70
71 def check_crash(fnc, args=None):
72     try:
73         fnc(args) if args else fnc()
74     except:
75         return
76     print_fail_msg_and_exit("test failed")
77
78
79 def init():
80     bpy.utils.register_class(TestClass)
81     bpy.types.Object.prop_array = bpy.props.CollectionProperty(
82         name="prop_array",
83         type=TestClass)
84     bpy.types.Object.prop = bpy.props.PointerProperty(type=bpy.types.Object)
85
86
87 def make_lib():
88     bpy.ops.wm.read_factory_settings()
89
90     # datablock pointer to the Camera object
91     bpy.data.objects["Cube"].prop = bpy.data.objects['Camera']
92
93     # array of datablock pointers to the Light object
94     for i in range(0, arr_len):
95         a = bpy.data.objects["Cube"].prop_array.add()
96         a.test_prop = bpy.data.objects['Light']
97         a.name = a.test_prop.name
98
99     # make unique named copy of the cube
100     ob = bpy.data.objects["Cube"].copy()
101     bpy.context.collection.objects.link(ob)
102
103     bpy.data.objects["Cube.001"].name = "Unique_Cube"
104
105     # duplicating of Cube
106     for i in range(0, ob_cp_count):
107         ob = bpy.data.objects["Cube"].copy()
108         bpy.context.collection.objects.link(ob)
109
110     # nodes
111     bpy.data.scenes["Scene"].use_nodes = True
112     bpy.data.scenes["Scene"].node_tree.nodes['Render Layers']["prop"] =\
113         bpy.data.objects['Camera']
114
115     # rename scene and save
116     bpy.data.scenes["Scene"].name = "Scene_lib"
117     bpy.ops.wm.save_as_mainfile(filepath=lib_path)
118
119
120 def check_lib():
121     # check pointer
122     abort_if_false(bpy.data.objects["Cube"].prop == bpy.data.objects['Camera'])
123
124     # check array of pointers in duplicated object
125     for i in range(0, arr_len):
126         abort_if_false(bpy.data.objects["Cube.001"].prop_array[i].test_prop ==
127                        bpy.data.objects['Light'])
128
129
130 def check_lib_linking():
131     # open startup file
132     bpy.ops.wm.read_factory_settings()
133
134     # link scene to the startup file
135     with bpy.data.libraries.load(lib_path, link=True) as (data_from, data_to):
136         data_to.scenes = ["Scene_lib"]
137
138     o = bpy.data.scenes["Scene_lib"].objects['Unique_Cube']
139
140     abort_if_false(o.prop_array[0].test_prop == bpy.data.scenes["Scene_lib"].objects['Light'])
141     abort_if_false(o.prop == bpy.data.scenes["Scene_lib"].objects['Camera'])
142     abort_if_false(o.prop.library == o.library)
143
144     bpy.ops.wm.save_as_mainfile(filepath=test_path)
145
146
147 def check_linked_scene_copying():
148     # full copy of the scene with datablock props
149     bpy.ops.wm.open_mainfile(filepath=test_path)
150     bpy.context.window.scene = bpy.data.scenes["Scene_lib"]
151     bpy.ops.scene.new(type='FULL_COPY')
152
153     # check save/open
154     bpy.ops.wm.save_as_mainfile(filepath=test_path)
155     bpy.ops.wm.open_mainfile(filepath=test_path)
156
157     intern_sce = get_scene(None, "Scene_lib")
158     extern_sce = get_scene("lib.blend", "Scene_lib")
159
160     # check node's props
161     # we made full copy from linked scene, so pointers must equal each other
162     abort_if_false(intern_sce.node_tree.nodes['Render Layers']["prop"] and
163                    intern_sce.node_tree.nodes['Render Layers']["prop"] ==
164                    extern_sce.node_tree.nodes['Render Layers']["prop"])
165
166
167 def check_scene_copying():
168     # full copy of the scene with datablock props
169     bpy.ops.wm.open_mainfile(filepath=lib_path)
170     bpy.context.window.scene = bpy.data.scenes["Scene_lib"]
171     bpy.ops.scene.new(type='FULL_COPY')
172
173     path = test_path + "_"
174     # check save/open
175     bpy.ops.wm.save_as_mainfile(filepath=path)
176     bpy.ops.wm.open_mainfile(filepath=path)
177
178     first_sce = get_scene(None, "Scene_lib")
179     second_sce = get_scene(None, "Scene_lib.001")
180
181     # check node's props
182     # must point to own scene camera
183     abort_if_false(not (first_sce.node_tree.nodes['Render Layers']["prop"] ==
184                         second_sce.node_tree.nodes['Render Layers']["prop"]))
185
186
187 # count users
188 def test_users_counting():
189     bpy.ops.wm.read_factory_settings()
190     Light_us = bpy.data.objects["Light"].data.users
191     n = 1000
192     for i in range(0, n):
193         bpy.data.objects["Cube"]["a%s" % i] = bpy.data.objects["Light"].data
194     abort_if_false(bpy.data.objects["Light"].data.users == Light_us + n)
195
196     for i in range(0, int(n / 2)):
197         bpy.data.objects["Cube"]["a%s" % i] = 1
198     abort_if_false(bpy.data.objects["Light"].data.users == Light_us + int(n / 2))
199
200
201 # linking
202 def test_linking():
203     make_lib()
204     check_lib()
205     check_lib_linking()
206     check_linked_scene_copying()
207     check_scene_copying()
208
209
210 # check restrictions for datablock pointers for some classes; GUI for manual testing
211 def test_restrictions1():
212     class TEST_Op(bpy.types.Operator):
213         bl_idname = 'scene.test_op'
214         bl_label = 'Test'
215         bl_options = {"INTERNAL"}
216         str_prop = bpy.props.StringProperty(name="str_prop")
217
218         # disallow registration of datablock properties in operators
219         # will be checked in the draw method (test manually)
220         # also, see console:
221         #   ValueError: bpy_struct "SCENE_OT_test_op" doesn't support datablock properties
222         id_prop = bpy.props.PointerProperty(type=bpy.types.Object)
223
224         def execute(self, context):
225             return {'FINISHED'}
226
227     # just panel for testing the poll callback with lots of objects
228     class TEST_PT_DatablockProp(bpy.types.Panel):
229         bl_label = "Datablock IDProp"
230         bl_space_type = "PROPERTIES"
231         bl_region_type = "WINDOW"
232         bl_context = "render"
233
234         def draw(self, context):
235             self.layout.prop_search(context.scene, "prop", bpy.data,
236                                     "objects")
237             self.layout.template_ID(context.scene, "prop1")
238             self.layout.prop_search(context.scene, "prop2", bpy.data, "node_groups")
239
240             op = self.layout.operator("scene.test_op")
241             op.str_prop = "test string"
242
243             def test_fnc(op):
244                 op["ob"] = bpy.data.objects['Unique_Cube']
245             check_crash(test_fnc, op)
246             abort_if_false(not hasattr(op, "id_prop"))
247
248     bpy.utils.register_class(TEST_PT_DatablockProp)
249     bpy.utils.register_class(TEST_Op)
250
251     def poll(self, value):
252         return value.name in bpy.data.scenes["Scene_lib"].objects
253
254     def poll1(self, value):
255         return True
256
257     bpy.types.Scene.prop = bpy.props.PointerProperty(type=bpy.types.Object)
258     bpy.types.Scene.prop1 = bpy.props.PointerProperty(type=bpy.types.Object, poll=poll)
259     bpy.types.Scene.prop2 = bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll1)
260
261     # check poll effect on UI (poll returns false => red alert)
262     bpy.context.scene.prop = bpy.data.objects["Light.001"]
263     bpy.context.scene.prop1 = bpy.data.objects["Light.001"]
264
265     # check incorrect type assignment
266     def sub_test():
267         # NodeTree id_prop
268         bpy.context.scene.prop2 = bpy.data.objects["Light.001"]
269
270     check_crash(sub_test)
271
272     bpy.context.scene.prop2 = bpy.data.node_groups.new("Shader", "ShaderNodeTree")
273
274     print("Please, test GUI performance manually on the Render tab, '%s' panel" %
275           TEST_PT_DatablockProp.bl_label, file=sys.stderr)
276     sys.stderr.flush()
277
278
279 # check some possible regressions
280 def test_regressions():
281     bpy.types.Object.prop_str = bpy.props.StringProperty(name="str")
282     bpy.data.objects["Unique_Cube"].prop_str = "test"
283
284     bpy.types.Object.prop_gr = bpy.props.PointerProperty(
285         name="prop_gr",
286         type=TestClass,
287         description="test")
288
289     bpy.data.objects["Unique_Cube"].prop_gr = None
290
291
292 # test restrictions for datablock pointers
293 def test_restrictions2():
294     class TestClassCollection(bpy.types.PropertyGroup):
295         prop = bpy.props.CollectionProperty(
296             name="prop_array",
297             type=TestClass)
298     bpy.utils.register_class(TestClassCollection)
299
300     class TestPrefs(bpy.types.AddonPreferences):
301         bl_idname = "testprefs"
302         # expecting crash during registering
303         my_prop2 = bpy.props.PointerProperty(type=TestClass)
304
305         prop = bpy.props.PointerProperty(
306             name="prop",
307             type=TestClassCollection,
308             description="test")
309
310     bpy.types.Addon.a = bpy.props.PointerProperty(type=bpy.types.Object)
311
312     class TestUIList(UIList):
313         test = bpy.props.PointerProperty(type=bpy.types.Object)
314
315         def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
316             layout.prop(item, "name", text="", emboss=False, icon_value=icon)
317
318     check_crash(bpy.utils.register_class, TestPrefs)
319     check_crash(bpy.utils.register_class, TestUIList)
320
321     bpy.utils.unregister_class(TestClassCollection)
322
323
324 def main():
325     init()
326     test_users_counting()
327     test_linking()
328     test_restrictions1()
329     check_crash(test_regressions)
330     test_restrictions2()
331
332
333 if __name__ == "__main__":
334     try:
335         main()
336     except:
337         import traceback
338
339         traceback.print_exc()
340         sys.stderr.flush()
341         os._exit(1)