bl_pyapi_idprop_datablock.py 11 KB

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