Sun position: remove unused prop in HDRI mode
[blender-addons.git] / amaranth / scene / debug.py
blobc4962b5dcac83c1ba634d0727f98d379f0a59417
1 # This program is free software; you can redistribute it and/or
2 # modify it under the terms of the GNU General Public License
3 # as published by the Free Software Foundation; either version 2
4 # of the License, or (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software Foundation,
13 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
14 """
15 Scene Debug Panel
17 This is something I've been wanting to have for a while, a way to know
18 certain info about your scene. A way to "debug" it, especially when
19 working in production with other teams, this came in very handy.
21 Being mostly a lighting guy myself, I needed two main features to start with:
23 * List Cycles Material using X shader
24 Where X is any shader type you want. It will display (and print on console)
25 a list of all the materials containing the shader you specified above.
26 Good for finding out if there's any Meshlight (Emission) material hidden,
27 or if there are many glossy shaders making things noisy.
28 A current limitation is that it doesn't look inside node groups (yet,
29 working on it!). It works since 0.8.8!
31 Under the "Scene Debug" panel in Scene properties.
33 * Lighter's Corner
34 This is an UI List of Lights in the scene(s).
35 It allows you to quickly see how many lights you have, select them by
36 clicking on their name, see their type (icon), samples number (if using
37 Branched Path Tracing), size, and change their visibility.
39 """
41 # TODO: module cleanup! maybe break it up in a package
42 # dicts instead of if, elif, else all over the place.
43 # helper functions instead of everything on the execute method.
44 # str.format() + dicts instead of inline % op all over the place.
45 # remove/manage debug print calls.
46 # avoid duplicate code/patterns through helper functions.
48 import os
49 import bpy
50 from amaranth import utils
51 from bpy.types import (
52 Operator,
53 Panel,
54 UIList,
55 PropertyGroup,
57 from bpy.props import (
58 BoolProperty,
59 CollectionProperty,
60 EnumProperty,
61 IntProperty,
62 PointerProperty,
63 StringProperty,
66 # default string used in the List Users for Datablock section menus
67 USER_X_NAME_EMPTY = "Data Block not selected/existing"
70 class AMTH_store_data():
71 # used by: AMTH_SCENE_OT_list_users_for_x operator
72 users = {
73 'OBJECT_DATA': [], # Store Objects with Material
74 'MATERIAL': [], # Materials (Node tree)
75 'LIGHT': [], # Lights
76 'WORLD': [], # World
77 'TEXTURE': [], # Textures (Psys, Brushes)
78 'MODIFIER': [], # Modifiers
79 'MESH_DATA': [], # Vertex Colors
80 'VIEW3D': [], # Background Images
81 'NODETREE': [], # Compositor
83 libraries = [] # Libraries x type
85 # used by: AMTH_SCENE_OT_list_missing_material_slots operator
86 obj_mat_slots = [] # Missing material slots
87 obj_mat_slots_lib = [] # Libraries with missing material slots
89 # used by: AMTH_SCENE_OT_cycles_shader_list_nodes operator
90 mat_shaders = [] # Materials that use a specific shader
92 # used by : AMTH_SCENE_OT_list_missing_node_links operator
93 count_groups = 0 # Missing node groups count
94 count_images = 0 # Missing node images
95 count_image_node_unlinked = 0 # Unlinked Image nodes
98 def call_update_datablock_type(self, context):
99 try:
100 # Note: this is pretty weak, but updates the operator enum selection
101 bpy.ops.scene.amth_list_users_for_x_type(list_type_select='0')
102 except:
103 pass
106 def init():
107 scene = bpy.types.Scene
109 scene.amaranth_lighterscorner_list_meshlights = BoolProperty(
110 default=False,
111 name="List Meshlights",
112 description="Include light emitting meshes on the list"
114 amth_datablock_types = (
115 ("IMAGE_DATA", "Image", "Image Datablocks", 0),
116 ("MATERIAL", "Material", "Material Datablocks", 1),
117 ("GROUP_VCOL", "Vertex Colors", "Vertex Color Layers", 2),
119 scene.amth_datablock_types = EnumProperty(
120 items=amth_datablock_types,
121 name="Type",
122 description="Datablock Type",
123 default="MATERIAL",
124 update=call_update_datablock_type,
125 options={"SKIP_SAVE"}
127 if utils.cycles_exists():
128 cycles_shader_node_types = (
129 ("BSDF_DIFFUSE", "Diffuse BSDF", "", 0),
130 ("BSDF_GLOSSY", "Glossy BSDF", "", 1),
131 ("BSDF_TRANSPARENT", "Transparent BSDF", "", 2),
132 ("BSDF_REFRACTION", "Refraction BSDF", "", 3),
133 ("BSDF_GLASS", "Glass BSDF", "", 4),
134 ("BSDF_TRANSLUCENT", "Translucent BSDF", "", 5),
135 ("BSDF_ANISOTROPIC", "Anisotropic BSDF", "", 6),
136 ("BSDF_VELVET", "Velvet BSDF", "", 7),
137 ("BSDF_TOON", "Toon BSDF", "", 8),
138 ("SUBSURFACE_SCATTERING", "Subsurface Scattering", "", 9),
139 ("EMISSION", "Emission", "", 10),
140 ("BSDF_HAIR", "Hair BSDF", "", 11),
141 ("BACKGROUND", "Background", "", 12),
142 ("AMBIENT_OCCLUSION", "Ambient Occlusion", "", 13),
143 ("HOLDOUT", "Holdout", "", 14),
144 ("VOLUME_ABSORPTION", "Volume Absorption", "", 15),
145 ("VOLUME_SCATTER", "Volume Scatter", "", 16),
146 ("MIX_SHADER", "Mix Shader", "", 17),
147 ("ADD_SHADER", "Add Shader", "", 18),
148 ('BSDF_PRINCIPLED', 'Principled BSDF', "", 19),
150 scene.amaranth_cycles_node_types = EnumProperty(
151 items=cycles_shader_node_types,
152 name="Shader"
156 def clear():
157 props = (
158 "amaranth_cycles_node_types",
159 "amaranth_lighterscorner_list_meshlights",
161 wm = bpy.context.window_manager
162 for p in props:
163 if wm.get(p):
164 del wm[p]
167 def print_with_count_list(text="", send_list=[]):
168 if text:
169 print("\n* {}\n".format(text))
170 if not send_list:
171 print("List is empty, no items to display")
172 return
174 for i, entry in enumerate(send_list):
175 print('{:02d}. {}'.format(i + 1, send_list[i]))
176 print("\n")
179 def print_grammar(line="", single="", multi="", cond=[]):
180 phrase = single if len(cond) == 1 else multi
181 print("\n* {} {}:\n".format(line, phrase))
184 def reset_global_storage(what="NONE"):
185 if what == "NONE":
186 return
188 if what == "XTYPE":
189 for user in AMTH_store_data.users:
190 AMTH_store_data.users[user] = []
191 AMTH_store_data.libraries = []
193 elif what == "MAT_SLOTS":
194 AMTH_store_data.obj_mat_slots[:] = []
195 AMTH_store_data.obj_mat_slots_lib[:] = []
197 elif what == "NODE_LINK":
198 AMTH_store_data.obj_mat_slots[:] = []
199 AMTH_store_data.count_groups = 0
200 AMTH_store_data.count_images = 0
201 AMTH_store_data.count_image_node_unlinked = 0
203 elif what == "SHADER":
204 AMTH_store_data.mat_shaders[:] = []
207 class AMTH_SCENE_OT_cycles_shader_list_nodes(Operator):
208 """List Cycles materials containing a specific shader"""
209 bl_idname = "scene.cycles_list_nodes"
210 bl_label = "List Materials"
212 @classmethod
213 def poll(cls, context):
214 return utils.cycles_exists() and utils.cycles_active(context)
216 def execute(self, context):
217 node_type = context.scene.amaranth_cycles_node_types
218 roughness = False
219 shaders_roughness = ("BSDF_GLOSSY", "BSDF_DIFFUSE", "BSDF_GLASS")
221 reset_global_storage("SHADER")
223 print("\n=== Cycles Shader Type: {} === \n".format(node_type))
225 for ma in bpy.data.materials:
226 if not ma.node_tree:
227 continue
229 nodes = ma.node_tree.nodes
230 print_unconnected = (
231 "Note: \nOutput from \"{}\" node in material \"{}\" "
232 "not connected\n".format(node_type, ma.name)
235 for no in nodes:
236 if no.type == node_type:
237 for ou in no.outputs:
238 if ou.links:
239 connected = True
240 if no.type in shaders_roughness:
241 roughness = "R: {:.4f}".format(
242 no.inputs["Roughness"].default_value
244 else:
245 roughness = False
246 else:
247 connected = False
248 print(print_unconnected)
250 if ma.name not in AMTH_store_data.mat_shaders:
251 AMTH_store_data.mat_shaders.append(
252 "%s%s [%s] %s%s%s" %
253 ("[L] " if ma.library else "",
254 ma.name,
255 ma.users,
256 "[F]" if ma.use_fake_user else "",
257 " - [%s]" %
258 roughness if roughness else "",
259 " * Output not connected" if not connected else "")
261 elif no.type == "GROUP":
262 if no.node_tree:
263 for nog in no.node_tree.nodes:
264 if nog.type == node_type:
265 for ou in nog.outputs:
266 if ou.links:
267 connected = True
268 if nog.type in shaders_roughness:
269 roughness = "R: {:.4f}".format(
270 nog.inputs["Roughness"].default_value
272 else:
273 roughness = False
274 else:
275 connected = False
276 print(print_unconnected)
278 if ma.name not in AMTH_store_data.mat_shaders:
279 AMTH_store_data.mat_shaders.append(
280 '%s%s%s [%s] %s%s%s' %
281 ("[L] " if ma.library else "",
282 "Node Group: %s%s -> " %
283 ("[L] " if no.node_tree.library else "",
284 no.node_tree.name),
285 ma.name,
286 ma.users,
287 "[F]" if ma.use_fake_user else "",
288 " - [%s]" %
289 roughness if roughness else "",
290 " * Output not connected" if not connected else "")
292 AMTH_store_data.mat_shaders = sorted(list(set(AMTH_store_data.mat_shaders)))
294 message = "No materials with nodes type {} found".format(node_type)
295 if len(AMTH_store_data.mat_shaders) > 0:
296 message = "A total of {} {} using {} found".format(
297 len(AMTH_store_data.mat_shaders),
298 "material" if len(AMTH_store_data.mat_shaders) == 1 else "materials",
299 node_type)
300 print_with_count_list(send_list=AMTH_store_data.mat_shaders)
302 self.report({'INFO'}, message)
303 AMTH_store_data.mat_shaders = sorted(list(set(AMTH_store_data.mat_shaders)))
305 return {"FINISHED"}
308 class AMTH_SCENE_OT_amaranth_object_select(Operator):
309 """Select object"""
310 bl_idname = "scene.amaranth_object_select"
311 bl_label = "Select Object"
313 object_name: StringProperty()
315 def execute(self, context):
316 if not (self.object_name and self.object_name in bpy.data.objects):
317 self.report({'WARNING'},
318 "Object with the given name could not be found. Operation Cancelled")
319 return {"CANCELLED"}
321 obj = bpy.data.objects[self.object_name]
323 bpy.ops.object.select_all(action="DESELECT")
324 obj.select_set(True)
325 context.view_layer.objects.active = obj
327 return {"FINISHED"}
330 class AMTH_SCENE_OT_list_missing_node_links(Operator):
331 """Print a list of missing node links"""
332 bl_idname = "scene.list_missing_node_links"
333 bl_label = "List Missing Node Links"
335 def execute(self, context):
336 missing_groups = []
337 missing_images = []
338 image_nodes_unlinked = []
339 libraries = []
341 reset_global_storage(what="NODE_LINK")
343 for ma in bpy.data.materials:
344 if not ma.node_tree:
345 continue
347 for no in ma.node_tree.nodes:
348 if no.type == "GROUP":
349 if not no.node_tree:
350 AMTH_store_data.count_groups += 1
352 users_ngroup = []
354 for ob in bpy.data.objects:
355 if ob.material_slots and ma.name in ob.material_slots:
356 users_ngroup.append("%s%s%s" % (
357 "[L] " if ob.library else "",
358 "[F] " if ob.use_fake_user else "",
359 ob.name))
361 missing_groups.append(
362 "MA: %s%s%s [%s]%s%s%s\n" %
363 ("[L] " if ma.library else "",
364 "[F] " if ma.use_fake_user else "",
365 ma.name,
366 ma.users,
367 " *** No users *** " if ma.users == 0 else "",
368 "\nLI: %s" %
369 ma.library.filepath if ma.library else "",
370 "\nOB: %s" %
371 ", ".join(users_ngroup) if users_ngroup else "")
373 if ma.library:
374 libraries.append(ma.library.filepath)
376 if no.type == "TEX_IMAGE":
378 outputs_empty = not no.outputs["Color"].is_linked and \
379 not no.outputs["Alpha"].is_linked
381 if no.image:
382 image_path_exists = os.path.exists(
383 bpy.path.abspath(
384 no.image.filepath,
385 library=no.image.library)
388 if outputs_empty or not no.image or not image_path_exists:
390 users_images = []
392 for ob in bpy.data.objects:
393 if ob.material_slots and ma.name in ob.material_slots:
394 users_images.append("%s%s%s" % (
395 "[L] " if ob.library else "",
396 "[F] " if ob.use_fake_user else "",
397 ob.name))
399 if outputs_empty:
400 AMTH_store_data.count_image_node_unlinked += 1
402 image_nodes_unlinked.append(
403 "%s%s%s%s%s [%s]%s%s%s%s%s\n" %
404 ("NO: %s" %
405 no.name,
406 "\nMA: ",
407 "[L] " if ma.library else "",
408 "[F] " if ma.use_fake_user else "",
409 ma.name,
410 ma.users,
411 " *** No users *** " if ma.users == 0 else "",
412 "\nLI: %s" %
413 ma.library.filepath if ma.library else "",
414 "\nIM: %s" %
415 no.image.name if no.image else "",
416 "\nLI: %s" %
417 no.image.filepath if no.image and no.image.filepath else "",
418 "\nOB: %s" %
419 ', '.join(users_images) if users_images else ""))
421 if not no.image or not image_path_exists:
422 AMTH_store_data.count_images += 1
424 missing_images.append(
425 "MA: %s%s%s [%s]%s%s%s%s%s\n" %
426 ("[L] " if ma.library else "",
427 "[F] " if ma.use_fake_user else "",
428 ma.name,
429 ma.users,
430 " *** No users *** " if ma.users == 0 else "",
431 "\nLI: %s" %
432 ma.library.filepath if ma.library else "",
433 "\nIM: %s" %
434 no.image.name if no.image else "",
435 "\nLI: %s" %
436 no.image.filepath if no.image and no.image.filepath else "",
437 "\nOB: %s" %
438 ', '.join(users_images) if users_images else ""))
440 if ma.library:
441 libraries.append(ma.library.filepath)
443 # Remove duplicates and sort
444 missing_groups = sorted(list(set(missing_groups)))
445 missing_images = sorted(list(set(missing_images)))
446 image_nodes_unlinked = sorted(list(set(image_nodes_unlinked)))
447 libraries = sorted(list(set(libraries)))
449 print(
450 "\n\n== %s missing image %s, %s missing node %s and %s image %s unlinked ==" %
451 ("No" if AMTH_store_data.count_images == 0 else str(
452 AMTH_store_data.count_images),
453 "node" if AMTH_store_data.count_images == 1 else "nodes",
454 "no" if AMTH_store_data.count_groups == 0 else str(
455 AMTH_store_data.count_groups),
456 "group" if AMTH_store_data.count_groups == 1 else "groups",
457 "no" if AMTH_store_data.count_image_node_unlinked == 0 else str(
458 AMTH_store_data.count_image_node_unlinked),
459 "node" if AMTH_store_data.count_groups == 1 else "nodes")
461 # List Missing Node Groups
462 if missing_groups:
463 print_with_count_list("Missing Node Group Links", missing_groups)
465 # List Missing Image Nodes
466 if missing_images:
467 print_with_count_list("Missing Image Nodes Link", missing_images)
469 # List Image Nodes with its outputs unlinked
470 if image_nodes_unlinked:
471 print_with_count_list("Image Nodes Unlinked", image_nodes_unlinked)
473 if missing_groups or missing_images or image_nodes_unlinked:
474 if libraries:
475 print_grammar("That's bad, run check", "this library", "these libraries", libraries)
476 print_with_count_list(send_list=libraries)
477 else:
478 self.report({"INFO"}, "Yay! No missing node links")
480 if missing_groups and missing_images:
481 self.report(
482 {"WARNING"},
483 "%d missing image %s and %d missing node %s found" %
484 (AMTH_store_data.count_images,
485 "node" if AMTH_store_data.count_images == 1 else "nodes",
486 AMTH_store_data.count_groups,
487 "group" if AMTH_store_data.count_groups == 1 else "groups")
490 return {"FINISHED"}
493 class AMTH_SCENE_OT_list_missing_material_slots(Operator):
494 """List objects with empty material slots"""
495 bl_idname = "scene.list_missing_material_slots"
496 bl_label = "List Empty Material Slots"
498 def execute(self, context):
499 reset_global_storage("MAT_SLOTS")
501 for ob in bpy.data.objects:
502 for ma in ob.material_slots:
503 if not ma.material:
504 AMTH_store_data.obj_mat_slots.append('{}{}'.format(
505 '[L] ' if ob.library else '', ob.name))
506 if ob.library:
507 AMTH_store_data.obj_mat_slots_lib.append(ob.library.filepath)
509 AMTH_store_data.obj_mat_slots = sorted(list(set(AMTH_store_data.obj_mat_slots)))
510 AMTH_store_data.obj_mat_slots_lib = sorted(list(set(AMTH_store_data.obj_mat_slots_lib)))
512 if len(AMTH_store_data.obj_mat_slots) == 0:
513 self.report({"INFO"},
514 "No objects with empty material slots found")
515 return {"FINISHED"}
517 print(
518 "\n* A total of {} {} with empty material slots was found \n".format(
519 len(AMTH_store_data.obj_mat_slots),
520 "object" if len(AMTH_store_data.obj_mat_slots) == 1 else "objects")
522 print_with_count_list(send_list=AMTH_store_data.obj_mat_slots)
524 if AMTH_store_data.obj_mat_slots_lib:
525 print_grammar("Check", "this library", "these libraries",
526 AMTH_store_data.obj_mat_slots_lib
528 print_with_count_list(send_list=AMTH_store_data.obj_mat_slots_lib)
530 return {"FINISHED"}
533 class AMTH_SCENE_OT_list_users_for_x_type(Operator):
534 bl_idname = "scene.amth_list_users_for_x_type"
535 bl_label = "Select"
536 bl_description = "Select Datablock Name"
538 @staticmethod
539 def fill_where():
540 where = []
541 data_block = bpy.context.scene.amth_datablock_types
543 if data_block == 'IMAGE_DATA':
544 for im in bpy.data.images:
545 if im.name not in {'Render Result', 'Viewer Node'}:
546 where.append(im)
548 elif data_block == 'MATERIAL':
549 where = bpy.data.materials
551 elif data_block == 'GROUP_VCOL':
552 for ob in bpy.data.objects:
553 if ob.type == 'MESH':
554 for v in ob.data.vertex_colors:
555 if v and v not in where:
556 where.append(v)
557 where = list(set(where))
559 return where
561 def avail(self, context):
562 datablock_type = bpy.context.scene.amth_datablock_types
563 where = AMTH_SCENE_OT_list_users_for_x_type.fill_where()
564 items = [(str(i), x.name, x.name, datablock_type, i) for i, x in enumerate(where)]
565 items = sorted(list(set(items)))
566 if not items:
567 items = [('0', USER_X_NAME_EMPTY, USER_X_NAME_EMPTY, "INFO", 0)]
568 return items
570 list_type_select: EnumProperty(
571 items=avail,
572 name="Available",
573 options={"SKIP_SAVE"}
576 @classmethod
577 def poll(cls, context):
578 return bpy.context.scene.amth_datablock_types
580 def execute(self, context):
581 where = self.fill_where()
582 bpy.context.scene.amth_list_users_for_x_name = \
583 where[int(self.list_type_select)].name if where else USER_X_NAME_EMPTY
585 return {'FINISHED'}
588 class AMTH_SCENE_OT_list_users_for_x(Operator):
589 """List users for a particular datablock"""
590 bl_idname = "scene.amth_list_users_for_x"
591 bl_label = "List Users for Datablock"
593 name: StringProperty()
595 def execute(self, context):
596 d = bpy.data
597 x = self.name if self.name else context.scene.amth_list_users_for_x_name
599 if USER_X_NAME_EMPTY in x:
600 self.report({'INFO'},
601 "Please select a DataBlock name first. Operation Cancelled")
602 return {"CANCELLED"}
604 dtype = context.scene.amth_datablock_types
606 reset_global_storage("XTYPE")
608 # IMAGE TYPE
609 if dtype == 'IMAGE_DATA':
610 # Check Materials
611 for ma in d.materials:
612 # Cycles
613 if utils.cycles_exists():
614 if ma and ma.node_tree and ma.node_tree.nodes:
615 materials = []
617 for nd in ma.node_tree.nodes:
618 if nd and nd.type in {'TEX_IMAGE', 'TEX_ENVIRONMENT'}:
619 materials.append(nd)
621 if nd and nd.type == 'GROUP':
622 if nd.node_tree and nd.node_tree.nodes:
623 for ng in nd.node_tree.nodes:
624 if ng.type in {'TEX_IMAGE', 'TEX_ENVIRONMENT'}:
625 materials.append(ng)
627 for no in materials:
628 if no.image and no.image.name == x:
629 objects = []
631 for ob in d.objects:
632 if ma.name in ob.material_slots:
633 objects.append(ob.name)
634 links = False
636 for o in no.outputs:
637 if o.links:
638 links = True
640 name = '"{0}" {1}{2}'.format(
641 ma.name,
642 'in object: {0}'.format(objects) if objects else ' (unassigned)',
643 '' if links else ' (unconnected)')
645 if name not in AMTH_store_data.users['MATERIAL']:
646 AMTH_store_data.users['MATERIAL'].append(name)
647 # Check Lights
648 for la in d.lights:
649 # Cycles
650 if utils.cycles_exists():
651 if la and la.node_tree and la.node_tree.nodes:
652 for no in la.node_tree.nodes:
653 if no and \
654 no.type in {'TEX_IMAGE', 'TEX_ENVIRONMENT'} and \
655 no.image and no.image.name == x:
656 if la.name not in AMTH_store_data.users['LIGHT']:
657 AMTH_store_data.users['LIGHT'].append(la.name)
658 # Check World
659 for wo in d.worlds:
660 # Cycles
661 if utils.cycles_exists():
662 if wo and wo.node_tree and wo.node_tree.nodes:
663 for no in wo.node_tree.nodes:
664 if no and \
665 no.type in {'TEX_IMAGE', 'TEX_ENVIRONMENT'} and \
666 no.image and no.image.name == x:
667 if wo.name not in AMTH_store_data.users['WORLD']:
668 AMTH_store_data.users['WORLD'].append(wo.name)
669 # Check Textures
670 for te in d.textures:
671 if te and te.type == 'IMAGE' and te.image:
672 name = te.image.name
674 if name == x and \
675 name not in AMTH_store_data.users['TEXTURE']:
676 AMTH_store_data.users['TEXTURE'].append(te.name)
677 # Check Modifiers in Objects
678 for ob in d.objects:
679 for mo in ob.modifiers:
680 if mo.type in {'UV_PROJECT'}:
681 image = mo.image
683 if mo and image and image.name == x:
684 name = '"{0}" modifier in {1}'.format(mo.name, ob.name)
685 if name not in AMTH_store_data.users['MODIFIER']:
686 AMTH_store_data.users['MODIFIER'].append(name)
687 # Check Background Images in Viewports
688 for scr in d.screens:
689 for ar in scr.areas:
690 if ar.type == 'VIEW_3D':
691 if ar.spaces and \
692 ar.spaces.active and \
693 ar.spaces.active.background_images:
694 for bg in ar.spaces.active.background_images:
695 image = bg.image
697 if bg and image and image.name == x:
698 name = 'Background for 3D Viewport in Screen "{0}"'\
699 .format(scr.name)
700 if name not in AMTH_store_data.users['VIEW3D']:
701 AMTH_store_data.users['VIEW3D'].append(name)
702 # Check the Compositor
703 for sce in d.scenes:
704 if sce.node_tree and sce.node_tree.nodes:
705 nodes = []
706 for nd in sce.node_tree.nodes:
707 if nd.type == 'IMAGE':
708 nodes.append(nd)
709 elif nd.type == 'GROUP':
710 if nd.node_tree and nd.node_tree.nodes:
711 for ng in nd.node_tree.nodes:
712 if ng.type == 'IMAGE':
713 nodes.append(ng)
715 for no in nodes:
716 if no.image and no.image.name == x:
717 links = False
719 for o in no.outputs:
720 if o.links:
721 links = True
723 name = 'Node {0} in Compositor (Scene "{1}"){2}'.format(
724 no.name,
725 sce.name,
726 '' if links else ' (unconnected)')
728 if name not in AMTH_store_data.users['NODETREE']:
729 AMTH_store_data.users['NODETREE'].append(name)
730 # MATERIAL TYPE
731 if dtype == 'MATERIAL':
732 # Check Materials - Note: build an object_check list as only strings are stored
733 object_check = [d.objects[names] for names in AMTH_store_data.users['OBJECT_DATA'] if
734 names in d.objects]
735 for ob in d.objects:
736 for ma in ob.material_slots:
737 if ma.name == x:
738 if ma not in object_check:
739 AMTH_store_data.users['OBJECT_DATA'].append(ob.name)
741 if ob.library:
742 AMTH_store_data.libraries.append(ob.library.filepath)
743 # VERTEX COLOR TYPE
744 elif dtype == 'GROUP_VCOL':
745 # Check VCOL in Meshes
746 for ob in bpy.data.objects:
747 if ob.type == 'MESH':
748 for v in ob.data.vertex_colors:
749 if v.name == x:
750 name = '{0}'.format(ob.name)
752 if name not in AMTH_store_data.users['MESH_DATA']:
753 AMTH_store_data.users['MESH_DATA'].append(name)
754 # Check VCOL in Materials
755 for ma in d.materials:
756 # Cycles
757 if utils.cycles_exists():
758 if ma and ma.node_tree and ma.node_tree.nodes:
759 for no in ma.node_tree.nodes:
760 if no and no.type in {'ATTRIBUTE'}:
761 if no.attribute_name == x:
762 objects = []
764 for ob in d.objects:
765 if ma.name in ob.material_slots:
766 objects.append(ob.name)
768 if objects:
769 name = '{0} in object: {1}'.format(ma.name, objects)
770 else:
771 name = '{0} (unassigned)'.format(ma.name)
773 if name not in AMTH_store_data.users['MATERIAL']:
774 AMTH_store_data.users['MATERIAL'].append(name)
776 AMTH_store_data.libraries = sorted(list(set(AMTH_store_data.libraries)))
778 # Print on console
779 empty = True
780 for t in AMTH_store_data.users:
781 if AMTH_store_data.users[t]:
782 empty = False
783 print('\n== {0} {1} use {2} "{3}" ==\n'.format(
784 len(AMTH_store_data.users[t]),
786 dtype,
788 for p in AMTH_store_data.users[t]:
789 print(' {0}'.format(p))
791 if AMTH_store_data.libraries:
792 print_grammar("Check", "this library", "these libraries",
793 AMTH_store_data.libraries
795 print_with_count_list(send_list=AMTH_store_data.libraries)
797 if empty:
798 self.report({'INFO'}, "No users for {}".format(x))
800 return {"FINISHED"}
803 class AMTH_SCENE_OT_list_users_debug_clear(Operator):
804 """Clear the list bellow"""
805 bl_idname = "scene.amth_list_users_debug_clear"
806 bl_label = "Clear Debug Panel lists"
808 what: StringProperty(
809 name="",
810 default="NONE",
811 options={'HIDDEN'}
814 def execute(self, context):
815 reset_global_storage(self.what)
817 return {"FINISHED"}
820 class AMTH_SCENE_OT_blender_instance_open(Operator):
821 """Open in a new Blender instance"""
822 bl_idname = "scene.blender_instance_open"
823 bl_label = "Open Blender Instance"
825 filepath: StringProperty()
827 def execute(self, context):
828 if self.filepath:
829 filepath = os.path.normpath(bpy.path.abspath(self.filepath))
831 import subprocess
832 try:
833 subprocess.Popen([bpy.app.binary_path, filepath])
834 except:
835 print("Error opening a new Blender instance")
836 import traceback
837 traceback.print_exc()
839 return {"FINISHED"}
842 class AMTH_SCENE_OT_Collection_List_Refresh(Operator):
843 bl_idname = "scene.amaranth_lighters_corner_refresh"
844 bl_label = "Refresh"
845 bl_description = ("Generate/Refresh the Lists\n"
846 "Use to generate/refresh the list or after changes to Data")
847 bl_options = {"REGISTER", "INTERNAL"}
849 what: StringProperty(default="NONE")
851 def execute(self, context):
852 message = "No changes applied"
854 if self.what == "LIGHTS":
855 fill_ligters_corner_props(context, refresh=True)
857 found_lights = len(context.window_manager.amth_lighters_state.keys())
858 message = "No Lights in the Data" if found_lights == 0 else \
859 "Generated list for {} found light(s)".format(found_lights)
861 elif self.what == "IMAGES":
862 fill_missing_images_props(context, refresh=True)
864 found_images = len(context.window_manager.amth_missing_images_state.keys())
865 message = "Great! No missing Images" if found_images == 0 else \
866 "Missing {} image(s) in the Data".format(found_images)
868 self.report({'INFO'}, message)
870 return {"FINISHED"}
873 class AMTH_SCENE_PT_scene_debug(Panel):
874 """Scene Debug"""
875 bl_label = "Scene Debug"
876 bl_space_type = "PROPERTIES"
877 bl_region_type = "WINDOW"
878 bl_context = "scene"
879 bl_options = {"DEFAULT_CLOSED"}
881 def draw_header(self, context):
882 layout = self.layout
883 layout.label(text="", icon="RADIOBUT_ON")
885 def draw_label(self, layout, body_text, single, multi, lists, ico="BLANK1"):
886 layout.label(
887 text="{} {} {}".format(
888 str(len(lists)), body_text,
889 single if len(lists) == 1 else multi),
890 icon=ico
893 def draw_miss_link(self, layout, text1, single, multi, text2, count, ico="BLANK1"):
894 layout.label(
895 text="{} {} {} {}".format(
896 count, text1,
897 single if count == 1 else multi, text2),
898 icon=ico
901 def draw(self, context):
902 layout = self.layout
903 scene = context.scene
905 has_images = len(bpy.data.images)
906 engine = scene.render.engine
908 # List Missing Images
909 box = layout.box()
910 split = box.split(factor=0.8, align=True)
911 row = split.row()
913 if has_images:
914 subrow = split.row(align=True)
915 subrow.alignment = "RIGHT"
916 subrow.operator(AMTH_SCENE_OT_Collection_List_Refresh.bl_idname,
917 text="", icon="FILE_REFRESH").what = "IMAGES"
918 image_state = context.window_manager.amth_missing_images_state
920 row.label(
921 text="{} Image Blocks present in the Data".format(has_images),
922 icon="IMAGE_DATA"
924 if len(image_state.keys()) > 0:
925 box.template_list(
926 'AMTH_UL_MissingImages_UI',
927 'amth_collection_index_prop',
928 context.window_manager,
929 'amth_missing_images_state',
930 context.window_manager.amth_collection_index_prop,
931 'index_image',
932 rows=3
934 else:
935 row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
937 # List Cycles Materials by Shader
938 if utils.cycles_exists() and engine == "CYCLES":
939 box = layout.box()
940 split = box.split()
941 col = split.column(align=True)
942 col.prop(scene, "amaranth_cycles_node_types",
943 icon="MATERIAL")
945 row = split.row(align=True)
946 row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes.bl_idname,
947 icon="SORTSIZE",
948 text="List Materials Using Shader")
949 if len(AMTH_store_data.mat_shaders) != 0:
950 row.operator(
951 AMTH_SCENE_OT_list_users_debug_clear.bl_idname,
952 icon="X", text="").what = "SHADER"
953 col.separator()
955 if len(AMTH_store_data.mat_shaders) != 0:
956 col = box.column(align=True)
957 self.draw_label(col, "found", "material", "materials",
958 AMTH_store_data.mat_shaders, "INFO"
960 for i, mat in enumerate(AMTH_store_data.mat_shaders):
961 col.label(
962 text="{}".format(AMTH_store_data.mat_shaders[i]), icon="MATERIAL"
965 # List Missing Node Trees
966 box = layout.box()
967 row = box.row(align=True)
968 split = row.split()
969 col = split.column(align=True)
971 split = col.split(align=True)
972 split.label(text="Node Links")
973 row = split.row(align=True)
974 row.operator(AMTH_SCENE_OT_list_missing_node_links.bl_idname,
975 icon="NODETREE")
977 if AMTH_store_data.count_groups != 0 or \
978 AMTH_store_data.count_images != 0 or \
979 AMTH_store_data.count_image_node_unlinked != 0:
981 row.operator(
982 AMTH_SCENE_OT_list_users_debug_clear.bl_idname,
983 icon="X", text="").what = "NODE_LINK"
984 col.label(text="Warning! Check Console", icon="ERROR")
986 if AMTH_store_data.count_groups != 0:
987 self.draw_miss_link(col, "node", "group", "groups", "missing link",
988 AMTH_store_data.count_groups, "NODE_TREE"
990 if AMTH_store_data.count_images != 0:
991 self.draw_miss_link(col, "image", "node", "nodes", "missing link",
992 AMTH_store_data.count_images, "IMAGE_DATA"
994 if AMTH_store_data.count_image_node_unlinked != 0:
995 self.draw_miss_link(col, "image", "node", "nodes", "with no output conected",
996 AMTH_store_data.count_image_node_unlinked, "NODE"
999 # List Empty Materials Slots
1000 box = layout.box()
1001 split = box.split()
1002 col = split.column(align=True)
1003 col.label(text="Material Slots")
1005 row = split.row(align=True)
1006 row.operator(AMTH_SCENE_OT_list_missing_material_slots.bl_idname,
1007 icon="MATERIAL",
1008 text="List Empty Materials Slots"
1010 if len(AMTH_store_data.obj_mat_slots) != 0:
1011 row.operator(
1012 AMTH_SCENE_OT_list_users_debug_clear.bl_idname,
1013 icon="X", text="").what = "MAT_SLOTS"
1015 col.separator()
1016 col = box.column(align=True)
1017 self.draw_label(col, "found empty material slot", "object", "objects",
1018 AMTH_store_data.obj_mat_slots, "INFO"
1020 for entry, obs in enumerate(AMTH_store_data.obj_mat_slots):
1021 row = col.row()
1022 row.alignment = "LEFT"
1023 row.label(
1024 text="{}".format(AMTH_store_data.obj_mat_slots[entry]),
1025 icon="OBJECT_DATA")
1027 if AMTH_store_data.obj_mat_slots_lib:
1028 col.separator()
1029 col.label("Check {}:".format(
1030 "this library" if
1031 len(AMTH_store_data.obj_mat_slots_lib) == 1 else
1032 "these libraries")
1034 for ilib, libs in enumerate(AMTH_store_data.obj_mat_slots_lib):
1035 row = col.row(align=True)
1036 row.alignment = "LEFT"
1037 row.operator(
1038 AMTH_SCENE_OT_blender_instance_open.bl_idname,
1039 text=AMTH_store_data.obj_mat_slots_lib[ilib],
1040 icon="LINK_BLEND",
1041 emboss=False).filepath = AMTH_store_data.obj_mat_slots_lib[ilib]
1043 box = layout.box()
1044 row = box.row(align=True)
1045 row.label(text="List Users for Datablock")
1047 col = box.column(align=True)
1048 split = col.split()
1049 row = split.row(align=True)
1050 row.prop(
1051 scene, "amth_datablock_types",
1052 icon=scene.amth_datablock_types,
1053 text=""
1055 row.operator_menu_enum(
1056 "scene.amth_list_users_for_x_type",
1057 "list_type_select",
1058 text=scene.amth_list_users_for_x_name
1061 row = split.row(align=True)
1062 row.enabled = True if USER_X_NAME_EMPTY not in scene.amth_list_users_for_x_name else False
1063 row.operator(
1064 AMTH_SCENE_OT_list_users_for_x.bl_idname,
1065 icon="COLLAPSEMENU").name = scene.amth_list_users_for_x_name
1067 if any(val for val in AMTH_store_data.users.values()):
1068 col = box.column(align=True)
1070 for t in AMTH_store_data.users:
1072 for ma in AMTH_store_data.users[t]:
1073 subrow = col.row(align=True)
1074 subrow.alignment = "LEFT"
1076 if t == 'OBJECT_DATA':
1077 text_lib = " [L] " if \
1078 ma in bpy.data.objects and bpy.data.objects[ma].library else ""
1079 subrow.operator(
1080 AMTH_SCENE_OT_amaranth_object_select.bl_idname,
1081 text="{} {}{}".format(text_lib, ma,
1082 "" if ma in context.scene.objects else " [Not in Scene]"),
1083 icon=t,
1084 emboss=False).object_name = ma
1085 else:
1086 subrow.label(text=ma, icon=t)
1087 row.operator(
1088 AMTH_SCENE_OT_list_users_debug_clear.bl_idname,
1089 icon="X", text="").what = "XTYPE"
1091 if AMTH_store_data.libraries:
1092 count_lib = 0
1094 col.separator()
1095 col.label("Check {}:".format(
1096 "this library" if
1097 len(AMTH_store_data.libraries) == 1 else
1098 "these libraries")
1100 for libs in AMTH_store_data.libraries:
1101 count_lib += 1
1102 row = col.row(align=True)
1103 row.alignment = "LEFT"
1104 row.operator(
1105 AMTH_SCENE_OT_blender_instance_open.bl_idname,
1106 text=AMTH_store_data.libraries[count_lib - 1],
1107 icon="LINK_BLEND",
1108 emboss=False).filepath = AMTH_store_data.libraries[count_lib - 1]
1111 class AMTH_PT_LightersCorner(Panel):
1112 """The Lighters Panel"""
1113 bl_label = "Lighter's Corner"
1114 bl_idname = "AMTH_SCENE_PT_lighters_corner"
1115 bl_space_type = 'PROPERTIES'
1116 bl_region_type = 'WINDOW'
1117 bl_context = "scene"
1118 bl_options = {"DEFAULT_CLOSED"}
1120 def draw_header(self, context):
1121 layout = self.layout
1122 layout.label(text="", icon="LIGHT_SUN")
1124 def draw(self, context):
1125 layout = self.layout
1126 state_props = len(context.window_manager.amth_lighters_state)
1127 engine = context.scene.render.engine
1128 box = layout.box()
1129 row = box.row(align=True)
1131 if utils.cycles_exists():
1132 row.prop(context.scene, "amaranth_lighterscorner_list_meshlights")
1134 subrow = row.row(align=True)
1135 subrow.alignment = "RIGHT"
1136 subrow.operator(AMTH_SCENE_OT_Collection_List_Refresh.bl_idname,
1137 text="", icon="FILE_REFRESH").what = "LIGHTS"
1139 if not state_props:
1140 row = box.row()
1141 message = "Please Refresh" if len(bpy.data.lights) > 0 else "No Lights in Data"
1142 row.label(text=message, icon="INFO")
1143 else:
1144 row = box.row(align=True)
1145 split = row.split(factor=0.5, align=True)
1146 col = split.column(align=True)
1148 col.label(text="Name/Library link")
1150 if engine in ["CYCLES", "BLENDER_RENDER"]:
1151 splits = 0.6 if engine == "BLENDER_RENDER" else 0.4
1152 splita = split.split(factor=splits, align=True)
1153 col = splita.column(align=True)
1154 col.alignment = "LEFT"
1155 col.label(text="Samples")
1157 if utils.cycles_exists() and engine == "CYCLES":
1158 col = splita.column(align=True)
1159 col.label(text="Size")
1161 cols = row.row(align=True)
1162 cols.alignment = "RIGHT"
1163 cols.label(text="{}Render Visibility/Selection".format(
1164 "Rays /" if utils.cycles_exists() else "")
1166 box.template_list(
1167 'AMTH_UL_LightersCorner_UI',
1168 'amth_collection_index_prop',
1169 context.window_manager,
1170 'amth_lighters_state',
1171 context.window_manager.amth_collection_index_prop,
1172 'index',
1173 rows=5
1177 class AMTH_UL_MissingImages_UI(UIList):
1179 def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
1180 text_lib = item.text_lib
1181 has_filepath = item.has_filepath
1182 is_library = item.is_library
1184 split = layout.split(factor=0.4)
1185 row = split.row(align=True)
1186 row.alignment = "LEFT"
1187 row.label(text=text_lib, icon="IMAGE_DATA")
1188 image = bpy.data.images.get(item.name, None)
1190 subrow = split.row(align=True)
1191 splitp = subrow.split(factor=0.8, align=True).row(align=True)
1192 splitp.alignment = "LEFT"
1193 row_lib = subrow.row(align=True)
1194 row_lib.alignment = "RIGHT"
1195 if not image:
1196 splitp.label(text="Image is not available", icon="ERROR")
1197 else:
1198 splitp.label(text=has_filepath, icon="LIBRARY_DATA_DIRECT")
1199 if is_library:
1200 row_lib.operator(
1201 AMTH_SCENE_OT_blender_instance_open.bl_idname,
1202 text="",
1203 emboss=False, icon="LINK_BLEND").filepath = is_library
1206 class AMTH_UL_LightersCorner_UI(UIList):
1208 def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
1209 icon_type = item.icon_type
1210 engine = context.scene.render.engine
1211 text_lib = item.text_lib
1212 is_library = item.is_library
1214 split = layout.split(factor=0.35)
1215 row = split.row(align=True)
1216 row.alignment = "LEFT"
1217 row.label(text=text_lib, icon=icon_type)
1218 ob = bpy.data.objects.get(item.name, None)
1219 if not ob:
1220 row.label(text="Object is not available", icon="ERROR")
1221 else:
1222 if is_library:
1223 row.operator(
1224 AMTH_SCENE_OT_blender_instance_open.bl_idname,
1225 text="",
1226 emboss=False, icon="LINK_BLEND").filepath = is_library
1228 rows = split.row(align=True)
1229 splits = 0.9 if engine == "BLENDER_RENDER" else 0.4
1230 splitlamp = rows.split(factor=splits, align=True)
1231 splitlampb = splitlamp.row(align=True)
1232 splitlampc = splitlamp.row(align=True)
1233 splitlampd = rows.row(align=True)
1234 splitlampd.alignment = "RIGHT"
1236 if utils.cycles_exists() and engine == "CYCLES":
1237 if "LIGHT" in icon_type:
1238 clamp = ob.data.cycles
1239 if context.scene.cycles.progressive == "BRANCHED_PATH":
1240 splitlampb.prop(clamp, "samples", text="")
1241 if context.scene.cycles.progressive == "PATH":
1242 splitlampb.label(text="N/A")
1243 lamp = ob.data
1244 if lamp.type in ["POINT", "SUN", "SPOT"]:
1245 splitlampc.label(text="{:.2f}".format(lamp.shadow_soft_size))
1246 elif lamp.type == "HEMI":
1247 splitlampc.label(text="N/A")
1248 elif lamp.type == "AREA" and lamp.shape == "RECTANGLE":
1249 splitlampc.label(
1250 text="{:.2f} x {:.2f}".format(lamp.size, lamp.size_y)
1252 else:
1253 splitlampc.label(text="{:.2f}".format(lamp.size))
1254 else:
1255 splitlampb.label(text="N/A")
1256 if engine == "BLENDER_RENDER":
1257 if "LIGHT" in icon_type:
1258 lamp = ob.data
1259 if lamp.type == "HEMI":
1260 splitlampb.label(text="Not Available")
1261 elif lamp.type == "AREA" and lamp.shadow_method == "RAY_SHADOW":
1262 splitlampb.prop(lamp, "shadow_ray_samples_x", text="X")
1263 if lamp.shape == "RECTANGLE":
1264 splitlampb.prop(lamp, "shadow_ray_samples_y", text="Y")
1265 elif lamp.shadow_method == "RAY_SHADOW":
1266 splitlampb.prop(lamp, "shadow_ray_samples", text="Ray Samples")
1267 elif lamp.shadow_method == "BUFFER_SHADOW":
1268 splitlampb.prop(lamp, "shadow_buffer_samples", text="Buffer Samples")
1269 else:
1270 splitlampb.label(text="No Shadow")
1271 else:
1272 splitlampb.label(text="N/A")
1273 if utils.cycles_exists():
1274 visibility = ob.cycles_visibility
1275 splitlampd.prop(visibility, "camera", text="")
1276 splitlampd.prop(visibility, "diffuse", text="")
1277 splitlampd.prop(visibility, "glossy", text="")
1278 splitlampd.prop(visibility, "shadow", text="")
1279 splitlampd.separator()
1280 splitlampd.prop(ob, "hide", text="", emboss=False)
1281 splitlampd.prop(ob, "hide_render", text="", emboss=False)
1282 splitlampd.operator(
1283 AMTH_SCENE_OT_amaranth_object_select.bl_idname,
1284 text="",
1285 emboss=False, icon="RESTRICT_SELECT_OFF").object_name = item.name
1288 def fill_missing_images_props(context, refresh=False):
1289 image_state = context.window_manager.amth_missing_images_state
1290 if refresh:
1291 for key in image_state.keys():
1292 index = image_state.find(key)
1293 if index != -1:
1294 image_state.remove(index)
1296 for im in bpy.data.images:
1297 if im.type not in ("UV_TEST", "RENDER_RESULT", "COMPOSITING"):
1298 if not im.packed_file and \
1299 not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
1300 text_l = "{}{} [{}]{}".format("[L] " if im.library else "", im.name,
1301 im.users, " [F]" if im.use_fake_user else "")
1302 prop = image_state.add()
1303 prop.name = im.name
1304 prop.text_lib = text_l
1305 prop.has_filepath = im.filepath if im.filepath else "No Filepath"
1306 prop.is_library = im.library.filepath if im.library else ""
1309 def fill_ligters_corner_props(context, refresh=False):
1310 light_state = context.window_manager.amth_lighters_state
1311 list_meshlights = context.scene.amaranth_lighterscorner_list_meshlights
1312 if refresh:
1313 for key in light_state.keys():
1314 index = light_state.find(key)
1315 if index != -1:
1316 light_state.remove(index)
1318 for ob in bpy.data.objects:
1319 if ob.name not in light_state.keys() or refresh:
1320 is_light = ob.type == "LIGHT"
1321 is_emission = True if utils.cycles_is_emission(
1322 context, ob) and list_meshlights else False
1324 if is_light or is_emission:
1325 icons = "LIGHT_%s" % ob.data.type if is_light else "MESH_GRID"
1326 text_l = "{} {}{}".format(" [L] " if ob.library else "", ob.name,
1327 "" if ob.name in context.scene.objects else " [Not in Scene]")
1328 prop = light_state.add()
1329 prop.name = ob.name
1330 prop.icon_type = icons
1331 prop.text_lib = text_l
1332 prop.is_library = ob.library.filepath if ob.library else ""
1335 class AMTH_LightersCornerStateProp(PropertyGroup):
1336 icon_type: StringProperty()
1337 text_lib: StringProperty()
1338 is_library: StringProperty()
1341 class AMTH_MissingImagesStateProp(PropertyGroup):
1342 text_lib: StringProperty()
1343 has_filepath: StringProperty()
1344 is_library: StringProperty()
1347 class AMTH_LightersCollectionIndexProp(PropertyGroup):
1348 index: IntProperty(
1349 name="index"
1351 index_image: IntProperty(
1352 name="index"
1356 classes = (
1357 AMTH_SCENE_PT_scene_debug,
1358 AMTH_SCENE_OT_list_users_debug_clear,
1359 AMTH_SCENE_OT_blender_instance_open,
1360 AMTH_SCENE_OT_amaranth_object_select,
1361 AMTH_SCENE_OT_list_missing_node_links,
1362 AMTH_SCENE_OT_list_missing_material_slots,
1363 AMTH_SCENE_OT_cycles_shader_list_nodes,
1364 AMTH_SCENE_OT_list_users_for_x,
1365 AMTH_SCENE_OT_list_users_for_x_type,
1366 AMTH_SCENE_OT_Collection_List_Refresh,
1367 AMTH_LightersCornerStateProp,
1368 AMTH_LightersCollectionIndexProp,
1369 AMTH_MissingImagesStateProp,
1370 AMTH_PT_LightersCorner,
1371 AMTH_UL_LightersCorner_UI,
1372 AMTH_UL_MissingImages_UI,
1376 def register():
1377 init()
1379 for cls in classes:
1380 bpy.utils.register_class(cls)
1382 bpy.types.Scene.amth_list_users_for_x_name = StringProperty(
1383 default="Select DataBlock Name",
1384 name="Name",
1385 description=USER_X_NAME_EMPTY,
1386 options={"SKIP_SAVE"}
1388 bpy.types.WindowManager.amth_collection_index_prop = PointerProperty(
1389 type=AMTH_LightersCollectionIndexProp
1391 bpy.types.WindowManager.amth_lighters_state = CollectionProperty(
1392 type=AMTH_LightersCornerStateProp
1394 bpy.types.WindowManager.amth_missing_images_state = CollectionProperty(
1395 type=AMTH_MissingImagesStateProp
1399 def unregister():
1400 clear()
1402 for cls in classes:
1403 bpy.utils.unregister_class(cls)
1405 del bpy.types.Scene.amth_list_users_for_x_name
1406 del bpy.types.WindowManager.amth_collection_index_prop
1407 del bpy.types.WindowManager.amth_lighters_state
1408 del bpy.types.WindowManager.amth_missing_images_state