Amaranth Addon
[blender-addons-contrib.git] / amaranth / scene / debug.py
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.
5 #
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.
10 #
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
16
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.
20
21 Being mostly a lighting guy myself, I needed two main features to start with:
22
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!
30
31 * Lamps List
32 This is a collapsable list of Lamps in the scene(s).
33 It allows you to quickly see how many lamps you have, select them by
34 clicking on their name, see their type (icon), samples number (if using
35 Branched Path Tracing), size, and change their visibility.
36 The active lamp is indicated by a triangle on the right.
37
38 Under the "Scene Debug" panel in Scene properties.
39 """
40
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 #     self.__class__.attr? use type(self).attr or self.attr instead.
47 #     avoid duplicate code/patterns through helper functions.
48
49 import os
50 import bpy
51 from amaranth import utils
52
53
54 def init():
55     scene = bpy.types.Scene
56
57     scene.amaranth_debug_scene_list_missing_images = bpy.props.BoolProperty(
58         default=False,
59         name="List Missing Images",
60         description="Display a list of all the missing images")
61
62     scene.amaranth_lighterscorner_list_meshlights = bpy.props.BoolProperty(
63         default=False,
64         name="List Meshlights",
65         description="Include light emitting meshes on the list")
66
67     amth_datablock_types = (
68         ("IMAGE_DATA", "Image", "Image Datablocks", 0),
69         ("MATERIAL", "Material", "Material Datablocks", 1),
70         ("GROUP_VCOL", "Vertex Colors", "Vertex Color Layers", 2),
71     )
72     scene.amth_datablock_types = bpy.props.EnumProperty(
73         items=amth_datablock_types,
74         name="Type",
75         description="Datablock Type")
76
77     if utils.cycles_exists():
78         cycles_shader_node_types = (
79             ("BSDF_DIFFUSE", "Diffuse BSDF", "", 0),
80             ("BSDF_GLOSSY", "Glossy BSDF", "", 1),
81             ("BSDF_TRANSPARENT", "Transparent BSDF", "", 2),
82             ("BSDF_REFRACTION", "Refraction BSDF", "", 3),
83             ("BSDF_GLASS", "Glass BSDF", "", 4),
84             ("BSDF_TRANSLUCENT", "Translucent BSDF", "", 5),
85             ("BSDF_ANISOTROPIC", "Anisotropic BSDF", "", 6),
86             ("BSDF_VELVET", "Velvet BSDF", "", 7),
87             ("BSDF_TOON", "Toon BSDF", "", 8),
88             ("SUBSURFACE_SCATTERING", "Subsurface Scattering", "", 9),
89             ("EMISSION", "Emission", "", 10),
90             ("BSDF_HAIR", "Hair BSDF", "", 11),
91             ("BACKGROUND", "Background", "", 12),
92             ("AMBIENT_OCCLUSION", "Ambient Occlusion", "", 13),
93             ("HOLDOUT", "Holdout", "", 14),
94             ("VOLUME_ABSORPTION", "Volume Absorption", "", 15),
95             ("VOLUME_SCATTER", "Volume Scatter", "", 16),
96             ("MIX_SHADER", "Mix Shader", "", 17),
97             ("ADD_SHADER", "Add Shader", "", 18),
98         )
99         scene.amaranth_cycles_node_types = bpy.props.EnumProperty(
100             items=cycles_shader_node_types, name="Shader")
101
102
103 def clear():
104     props = (
105         "amaranth_debug_scene_list_missing_images",
106         "amaranth_cycles_node_types",
107         "amaranth_lighterscorner_list_meshlights",
108     )
109     wm = bpy.context.window_manager
110     for p in props:
111         if wm.get(p):
112             del wm[p]
113
114
115 class AMTH_SCENE_OT_cycles_shader_list_nodes(bpy.types.Operator):
116
117     """List Cycles materials containing a specific shader"""
118     bl_idname = "scene.cycles_list_nodes"
119     bl_label = "List Materials"
120     materials = []
121
122     @classmethod
123     def poll(cls, context):
124         return utils.cycles_exists() and utils.cycles_active(context)
125
126     def execute(self, context):
127         node_type = context.scene.amaranth_cycles_node_types
128         roughness = False
129         self.__class__.materials = []
130         shaders_roughness = ("BSDF_GLOSSY", "BSDF_DIFFUSE", "BSDF_GLASS")
131
132         print("\n=== Cycles Shader Type: %s === \n" % node_type)
133
134         for ma in bpy.data.materials:
135             if ma.node_tree:
136                 nodes = ma.node_tree.nodes
137
138                 print_unconnected = (
139                     "Note: \nOutput from \"%s\" node" % node_type,
140                     "in material \"%s\"" % ma.name, "not connected\n")
141
142                 for no in nodes:
143                     if no.type == node_type:
144                         for ou in no.outputs:
145                             if ou.links:
146                                 connected = True
147                                 if no.type in shaders_roughness:
148                                     roughness = "R: %.4f" % no.inputs[
149                                         "Roughness"].default_value
150                                 else:
151                                     roughness = False
152                             else:
153                                 connected = False
154                                 print(print_unconnected)
155
156                             if ma.name not in self.__class__.materials:
157                                 self.__class__.materials.append(
158                                     "%s%s [%s] %s%s%s" %
159                                     ("[L] " if ma.library else "",
160                                      ma.name,
161                                      ma.users,
162                                      "[F]" if ma.use_fake_user else "",
163                                      " - [%s]" %
164                                      roughness if roughness else "",
165                                      " * Output not connected" if not connected else ""))
166
167                     elif no.type == "GROUP":
168                         if no.node_tree:
169                             for nog in no.node_tree.nodes:
170                                 if nog.type == node_type:
171                                     for ou in nog.outputs:
172                                         if ou.links:
173                                             connected = True
174                                             if nog.type in shaders_roughness:
175                                                 roughness = "R: %.4f" % nog.inputs[
176                                                     "Roughness"].default_value
177                                             else:
178                                                 roughness = False
179                                         else:
180                                             connected = False
181                                             print(print_unconnected)
182
183                                         if ma.name not in self.__class__.materials:
184                                             self.__class__.materials.append(
185                                                 '%s%s%s [%s] %s%s%s' %
186                                                 ("[L] " if ma.library else "",
187                                                  "Node Group:  %s%s  ->  " %
188                                                  ("[L] " if no.node_tree.library else "",
189                                                   no.node_tree.name),
190                                                     ma.name,
191                                                     ma.users,
192                                                     "[F]" if ma.use_fake_user else "",
193                                                     " - [%s]" %
194                                                     roughness if roughness else "",
195                                                     " * Output not connected" if not connected else ""))
196
197                     self.__class__.materials = sorted(
198                         list(set(self.__class__.materials)))
199
200         if len(self.__class__.materials) == 0:
201             self.report({"INFO"},
202                         "No materials with nodes type %s found" % node_type)
203         else:
204             print("* A total of %d %s using %s was found \n" % (
205                 len(self.__class__.materials),
206                 "material" if len(
207                     self.__class__.materials) == 1 else "materials",
208                 node_type))
209
210             count = 0
211
212             for mat in self.__class__.materials:
213                 print('%02d. %s' %
214                       (count + 1, self.__class__.materials[count]))
215                 count += 1
216             print("\n")
217
218         self.__class__.materials = sorted(list(set(self.__class__.materials)))
219
220         return {"FINISHED"}
221
222
223 class AMTH_SCENE_OT_cycles_shader_list_nodes_clear(bpy.types.Operator):
224
225     """Clear the list below"""
226     bl_idname = "scene.cycles_list_nodes_clear"
227     bl_label = "Clear Materials List"
228
229     @classmethod
230     def poll(cls, context):
231         return utils.cycles_exists()
232
233     def execute(self, context):
234         AMTH_SCENE_OT_cycles_shader_list_nodes.materials[:] = []
235         print("* Cleared Cycles Materials List")
236         return {"FINISHED"}
237
238
239 class AMTH_SCENE_OT_amaranth_object_select(bpy.types.Operator):
240
241     """Select object"""
242     bl_idname = "scene.amaranth_object_select"
243     bl_label = "Select Object"
244     object = bpy.props.StringProperty()
245
246     def execute(self, context):
247         if self.object:
248             object = bpy.data.objects[self.object]
249
250             bpy.ops.object.select_all(action="DESELECT")
251             object.select = True
252             context.scene.objects.active = object
253
254         return {"FINISHED"}
255
256
257 class AMTH_SCENE_OT_list_missing_node_links(bpy.types.Operator):
258
259     """Print a list of missing node links"""
260     bl_idname = "scene.list_missing_node_links"
261     bl_label = "List Missing Node Links"
262
263     count_groups = 0
264     count_images = 0
265     count_image_node_unlinked = 0
266
267     def execute(self, context):
268         missing_groups = []
269         missing_images = []
270         image_nodes_unlinked = []
271         libraries = []
272         self.__class__.count_groups = 0
273         self.__class__.count_images = 0
274         self.__class__.count_image_node_unlinked = 0
275
276         for ma in bpy.data.materials:
277             if ma.node_tree:
278                 for no in ma.node_tree.nodes:
279                     if no.type == "GROUP":
280                         if not no.node_tree:
281                             self.__class__.count_groups += 1
282
283                             users_ngroup = []
284
285                             for ob in bpy.data.objects:
286                                 if ob.material_slots and ma.name in ob.material_slots:
287                                     users_ngroup.append("%s%s%s" % (
288                                         "[L] " if ob.library else "",
289                                         "[F] " if ob.use_fake_user else "",
290                                         ob.name))
291
292                             missing_groups.append(
293                                 "MA: %s%s%s [%s]%s%s%s\n" %
294                                 ("[L] " if ma.library else "",
295                                  "[F] " if ma.use_fake_user else "",
296                                  ma.name,
297                                  ma.users,
298                                  " *** No users *** " if ma.users == 0 else "",
299                                  "\nLI: %s" %
300                                  ma.library.filepath if ma.library else "",
301                                  "\nOB: %s" %
302                                  ",  ".join(users_ngroup) if users_ngroup else ""))
303
304                             if ma.library:
305                                 libraries.append(ma.library.filepath)
306                     if no.type == "TEX_IMAGE":
307
308                         outputs_empty = not no.outputs[
309                             "Color"].is_linked and not no.outputs["Alpha"].is_linked
310
311                         if no.image:
312                             image_path_exists = os.path.exists(
313                                 bpy.path.abspath(
314                                     no.image.filepath,
315                                     library=no.image.library))
316
317                         if outputs_empty or not \
318                            no.image or not \
319                            image_path_exists:
320
321                             users_images = []
322
323                             for ob in bpy.data.objects:
324                                 if ob.material_slots and ma.name in ob.material_slots:
325                                     users_images.append("%s%s%s" % (
326                                         "[L] " if ob.library else "",
327                                         "[F] " if ob.use_fake_user else "",
328                                         ob.name))
329
330                             if outputs_empty:
331                                 self.__class__.count_image_node_unlinked += 1
332
333                                 image_nodes_unlinked.append(
334                                     "%s%s%s%s%s [%s]%s%s%s%s%s\n" %
335                                     ("NO: %s" %
336                                      no.name,
337                                      "\nMA: ",
338                                      "[L] " if ma.library else "",
339                                      "[F] " if ma.use_fake_user else "",
340                                      ma.name,
341                                      ma.users,
342                                      " *** No users *** " if ma.users == 0 else "",
343                                      "\nLI: %s" %
344                                      ma.library.filepath if ma.library else "",
345                                      "\nIM: %s" %
346                                      no.image.name if no.image else "",
347                                      "\nLI: %s" %
348                                      no.image.filepath if no.image and no.image.filepath else "",
349                                      "\nOB: %s" %
350                                      ',  '.join(users_images) if users_images else ""))
351
352                             if not no.image or not image_path_exists:
353                                 self.__class__.count_images += 1
354
355                                 missing_images.append(
356                                     "MA: %s%s%s [%s]%s%s%s%s%s\n" %
357                                     ("[L] " if ma.library else "",
358                                      "[F] " if ma.use_fake_user else "",
359                                      ma.name,
360                                      ma.users,
361                                      " *** No users *** " if ma.users == 0 else "",
362                                      "\nLI: %s" %
363                                      ma.library.filepath if ma.library else "",
364                                      "\nIM: %s" %
365                                      no.image.name if no.image else "",
366                                      "\nLI: %s" %
367                                      no.image.filepath if no.image and no.image.filepath else "",
368                                      "\nOB: %s" %
369                                      ',  '.join(users_images) if users_images else ""))
370
371                                 if ma.library:
372                                     libraries.append(ma.library.filepath)
373
374         # Remove duplicates and sort
375         missing_groups = sorted(list(set(missing_groups)))
376         missing_images = sorted(list(set(missing_images)))
377         image_nodes_unlinked = sorted(list(set(image_nodes_unlinked)))
378         libraries = sorted(list(set(libraries)))
379
380         print(
381             "\n\n== %s missing image %s, %s missing node %s and %s image %s unlinked ==" %
382             ("No" if self.__class__.count_images == 0 else str(
383                 self.__class__.count_images),
384                 "node" if self.__class__.count_images == 1 else "nodes",
385                 "no" if self.__class__.count_groups == 0 else str(
386                     self.__class__.count_groups),
387                 "group" if self.__class__.count_groups == 1 else "groups",
388                 "no" if self.__class__.count_image_node_unlinked == 0 else str(
389                     self.__class__.count_image_node_unlinked),
390                 "node" if self.__class__.count_groups == 1 else "nodes"))
391
392         # List Missing Node Groups
393         if missing_groups:
394             print("\n* Missing Node Group Links\n")
395             for mig in missing_groups:
396                 print(mig)
397
398         # List Missing Image Nodes
399         if missing_images:
400             print("\n* Missing Image Nodes Link\n")
401
402             for mii in missing_images:
403                 print(mii)
404
405         # List Image Nodes with its outputs unlinked
406         if image_nodes_unlinked:
407             print("\n* Image Nodes Unlinked\n")
408
409             for nou in image_nodes_unlinked:
410                 print(nou)
411
412         if missing_groups or \
413            missing_images or \
414            image_nodes_unlinked:
415             if libraries:
416                 print(
417                     "\nThat's bad, run check on %s:" %
418                     ("this library" if len(libraries) == 1 else "these libraries"))
419                 for li in libraries:
420                     print(li)
421         else:
422             self.report({"INFO"}, "Yay! No missing node links")
423
424         print("\n")
425
426         if missing_groups and missing_images:
427             self.report(
428                 {"WARNING"},
429                 "%d missing image %s and %d missing node %s found" %
430                 (self.__class__.count_images,
431                  "node" if self.__class__.count_images == 1 else "nodes",
432                  self.__class__.count_groups,
433                  "group" if self.__class__.count_groups == 1 else "groups"))
434
435         return {"FINISHED"}
436
437
438 class AMTH_SCENE_OT_list_missing_material_slots(bpy.types.Operator):
439
440     """List objects with empty material slots"""
441     bl_idname = "scene.list_missing_material_slots"
442     bl_label = "List Empty Material Slots"
443
444     objects = []
445     libraries = []
446
447     def execute(self, context):
448         self.__class__.objects = []
449         self.__class__.libraries = []
450
451         for ob in bpy.data.objects:
452             for ma in ob.material_slots:
453                 if not ma.material:
454                     self.__class__.objects.append('%s%s' % (
455                         '[L] ' if ob.library else '',
456                         ob.name))
457                     if ob.library:
458                         self.__class__.libraries.append(ob.library.filepath)
459
460         self.__class__.objects = sorted(list(set(self.__class__.objects)))
461         self.__class__.libraries = sorted(list(set(self.__class__.libraries)))
462
463         if len(self.__class__.objects) == 0:
464             self.report({"INFO"},
465                         "No objects with empty material slots found")
466         else:
467             print(
468                 "\n* A total of %d %s with empty material slots was found \n" %
469                 (len(
470                     self.__class__.objects), "object" if len(
471                     self.__class__.objects) == 1 else "objects"))
472
473             count = 0
474             count_lib = 0
475
476             for obs in self.__class__.objects:
477                 print('%02d. %s' % (
478                     count + 1, self.__class__.objects[count]))
479                 count += 1
480
481             if self.__class__.libraries:
482                 print("\n\n* Check %s:\n" %
483                      ("this library" if len(self.__class__.libraries) == 1
484                       else "these libraries"))
485
486                 for libs in self.__class__.libraries:
487                     print('%02d. %s' % (
488                         count_lib + 1, self.__class__.libraries[count_lib]))
489                     count_lib += 1
490             print("\n")
491
492         return {"FINISHED"}
493
494
495 class AMTH_SCENE_OT_list_missing_material_slots_clear(bpy.types.Operator):
496
497     """Clear the list below"""
498     bl_idname = "scene.list_missing_material_slots_clear"
499     bl_label = "Clear Empty Material Slots List"
500
501     def execute(self, context):
502         AMTH_SCENE_OT_list_missing_material_slots.objects[:] = []
503         print("* Cleared Empty Material Slots List")
504         return {"FINISHED"}
505
506
507 class AMTH_SCENE_OT_list_users_for_x_type(bpy.types.Operator):
508     bl_idname = "scene.amth_list_users_for_x_type"
509     bl_label = "Select"
510     bl_description = "Select Datablock Name"
511
512     def avail(self,context):
513         datablock_type = bpy.context.scene.amth_datablock_types
514
515         if datablock_type == 'IMAGE_DATA':
516             where = []
517             for im in bpy.data.images:
518                 if im.name not in {'Render Result', 'Viewer Node'}:
519                     where.append(im)
520
521         elif datablock_type == 'MATERIAL':
522             where = bpy.data.materials
523
524         elif datablock_type == 'GROUP_VCOL':
525             where = []
526             for ob in bpy.data.objects:
527                 if ob.type == 'MESH':
528                     for v in ob.data.vertex_colors:
529                         if v and v not in where:
530                             where.append(v)
531             where = list(set(where))
532
533         items = [(str(i),x.name,x.name, datablock_type, i) for i,x in enumerate(where)]
534         items = sorted(list(set(items)))
535         return items
536
537     list_type_select = bpy.props.EnumProperty(items = avail, name = "Available")
538
539     @classmethod
540     def poll(cls, context):
541         return bpy.context.scene.amth_datablock_types
542     
543     def execute(self,context):
544         datablock_type = bpy.context.scene.amth_datablock_types
545
546         if datablock_type == 'IMAGE_DATA':
547             where = []
548             for im in bpy.data.images:
549                 if im.name not in {'Render Result', 'Viewer Node'}:
550                     where.append(im)
551
552         elif datablock_type == 'MATERIAL':
553             where = bpy.data.materials
554
555         elif datablock_type == 'GROUP_VCOL':
556             where = []
557             for ob in bpy.data.objects:
558                 if ob.type == 'MESH':
559                     for v in ob.data.vertex_colors:
560                         if v and v not in where:
561                             where.append(v)
562             where = list(set(where))
563
564         bpy.context.scene.amth_list_users_for_x_name = where[int(self.list_type_select)].name
565         return {'FINISHED'}
566
567
568 class AMTH_SCENE_OT_list_users_for_x(bpy.types.Operator):
569
570     """List users for a particular datablock"""
571     bl_idname = "scene.amth_list_users_for_x"
572     bl_label = "List Users for Datablock"
573
574     name = bpy.props.StringProperty()
575     users = {}
576
577     def execute(self, context):
578
579         datablock_type = context.scene.amth_datablock_types
580         d = bpy.data
581
582         if self.name:
583             x = self.name
584         else:
585             x = context.scene.amth_list_users_for_x_name
586
587         dtype = context.scene.amth_datablock_types
588
589         self.__class__.users = {
590             'OBJECT_DATA' : [], # Store Objects with Material
591             'MATERIAL' : [], # Materials (Node tree)
592             'LAMP' : [], # Lamps
593             'WORLD' : [], # World
594             'TEXTURE' : [], # Textures (Psys, Brushes)
595             'MODIFIER' : [], # Modifiers
596             'MESH_DATA' : [], # Vertex Colors
597             'VIEW3D' : [], # Background Images
598             'NODETREE' : [], # Compositor
599         }
600
601         # IMAGE TYPE
602         if dtype == 'IMAGE_DATA':
603             # Check Materials
604             for ma in d.materials:
605                 # Cycles
606                 if utils.cycles_exists():
607                     if ma and ma.node_tree and ma.node_tree.nodes:
608                         materials = []
609
610                         for nd in ma.node_tree.nodes:
611                             if nd and nd.type in {'TEX_IMAGE','TEX_ENVIRONMENT'}:
612                                 materials.append(nd)
613                             if nd and nd.type == 'GROUP':
614                                 if nd.node_tree and nd.node_tree.nodes:
615                                     for ng in nd.node_tree.nodes:
616                                         if ng.type in {'TEX_IMAGE','TEX_ENVIRONMENT'}:
617                                             materials.append(ng)
618
619                             for no in materials:
620                                 if no.image and no.image.name == x:
621                                     objects = []
622
623                                     for ob in d.objects:
624                                         if ma.name in ob.material_slots:
625                                             objects.append(ob.name)
626
627                                     links = False
628
629                                     for o in no.outputs:
630                                         if o.links:
631                                             links = True
632
633                                     name = '"{0}" {1}{2}'.format(
634                                             ma.name,
635                                             'in object: {0}'.format(objects) if objects else ' (unassigned)',
636                                             '' if links else ' (unconnected)')
637
638                                     if name not in self.__class__.users['MATERIAL']:
639                                         self.__class__.users['MATERIAL'].append(name)
640
641             # Check Lamps
642             for la in d.lamps:
643                 # Cycles
644                 if utils.cycles_exists():
645                     if la and la.node_tree and la.node_tree.nodes:
646                         for no in la.node_tree.nodes:
647                             if no and \
648                                no.type in {'TEX_IMAGE','TEX_ENVIRONMENT'} and \
649                                no.image and no.image.name == x:
650                                     if la.name not in self.__class__.users['LAMP']:
651                                         self.__class__.users['LAMP'].append(la.name)
652
653             # Check World
654             for wo in d.worlds:
655                 # Cycles
656                 if utils.cycles_exists():
657                     if wo and wo.node_tree and wo.node_tree.nodes:
658                         for no in wo.node_tree.nodes:
659                             if no and \
660                                no.type in {'TEX_IMAGE','TEX_ENVIRONMENT'} and \
661                                no.image and no.image.name == x:
662                                 if wo.name not in self.__class__.users['WORLD']:
663                                     self.__class__.users['WORLD'].append(wo.name)
664
665             # Check Textures
666             for te in d.textures:
667                 if te and te.type =='IMAGE' and te.image:
668                     name = te.image.name
669
670                     if name == x and \
671                        name not in self.__class__.users['TEXTURE'] :
672                         self.__class__.users['TEXTURE'].append(te.name)
673
674             # Check Modifiers in Objects
675             for ob in d.objects:
676                 for mo in ob.modifiers:
677                     if mo.type in {'UV_PROJECT'}:
678                         image = mo.image
679
680                         if mo and image and image.name == x:
681                             name = '"{0}" modifier in {1}'.format(mo.name, ob.name)
682                             if name not in self.__class__.users['MODIFIER']:
683                                 self.__class__.users['MODIFIER'].append(name)
684
685             # Check Background Images in Viewports
686             for scr in d.screens:
687                 for ar in scr.areas:
688                     if ar.type == 'VIEW_3D':
689                         for bg in ar.spaces.active.background_images:
690                             image = bg.image
691
692                             if bg and image and image.name == x:
693                                 name = 'Background for 3D Viewport in Screen "{0}"'\
694                                         .format(scr.name)
695                                 if name not in self.__class__.users['VIEW3D']:
696                                     self.__class__.users['VIEW3D'].append(name)
697
698             # Check the Compositor
699             for sce in d.scenes:
700                 if sce.node_tree and sce.node_tree.nodes:
701                     nodes = []
702
703                     for nd in sce.node_tree.nodes:
704                         if nd.type == 'IMAGE':
705                             nodes.append(nd)
706                         elif nd.type == 'GROUP':
707                             if nd.node_tree and nd.node_tree.nodes:
708                                 for ng in nd.node_tree.nodes:
709                                     if ng.type == 'IMAGE':
710                                         nodes.append(ng)
711
712                         for no in nodes:
713                             if no.image and no.image.name == x:
714
715                                 links = False
716
717                                 for o in no.outputs:
718                                     if o.links:
719                                         links = True
720
721                                 name = 'Node {0} in Compositor (Scene "{1}"){2}'.format(
722                                         no.name,
723                                         sce.name,
724                                         '' if links else ' (unconnected)')
725
726                                 if name not in self.__class__.users['NODETREE']:
727                                     self.__class__.users['NODETREE'].append(name)
728
729         # MATERIAL TYPE
730         if dtype == 'MATERIAL':
731             # Check Materials
732             for ob in d.objects:
733                 for ma in ob.material_slots:
734                     if ma.name == x:
735                         if ma not in self.__class__.users['OBJECT_DATA']:
736                             self.__class__.users['OBJECT_DATA'].append(ob)
737
738         # VERTEX COLOR TYPE
739         elif dtype == 'GROUP_VCOL':
740             # Check VCOL in Meshes
741             for ob in bpy.data.objects:
742                 if ob.type == 'MESH':
743                     for v in ob.data.vertex_colors:
744                         if v.name == x:
745                             name = '{0}'.format(ob.name)
746
747                             if name not in self.__class__.users['MESH_DATA']:
748                                 self.__class__.users['MESH_DATA'].append(name)
749
750             # Check VCOL in Materials
751             for ma in d.materials:
752                 # Cycles
753                 if utils.cycles_exists():
754                     if ma and ma.node_tree and ma.node_tree.nodes:
755                         for no in ma.node_tree.nodes:
756                             if no and no.type in {'ATTRIBUTE'}:
757                                 if no.attribute_name == x:
758                                     objects = []
759
760                                     for ob in d.objects:
761                                         if ma.name in ob.material_slots:
762                                             objects.append(ob.name)
763
764                                     if objects:
765                                         name = '{0} in object: {1}'.format(ma.name, objects)
766                                     else:
767                                         name = '{0} (unassigned)'.format(ma.name)
768
769                                     if name not in self.__class__.users['MATERIAL']:
770                                         self.__class__.users['MATERIAL'].append(name)
771
772         # Print on console
773         empty = True
774
775         for t in self.__class__.users:
776             if self.__class__.users[t]:
777                 empty = False
778                 print('\n== {0} {1} use {2} "{3}" ==\n'.format(
779                         len(self.__class__.users[t]),
780                         t,
781                         dtype,
782                         x))
783                 for p in self.__class__.users[t]:
784                     print(' {0}'.format(p))
785         if empty:
786             print('\n== No users for {0} ==\n'.format(x))
787
788         #print('Type: {0}'.format(context.scene.amth_datablock_types))
789         #print('X: {0}'.format(x))
790
791         return {"FINISHED"}
792
793
794 class AMTH_SCENE_OT_list_users_for_x_clear(bpy.types.Operator):
795
796     """Clear the list below"""
797     bl_idname = "scene.amth_list_users_for_x_clear"
798     bl_label = "Clear Users Lists for X"
799
800     def execute(self, context):
801         AMTH_SCENE_OT_list_users_for_x.users = {}
802         print("* Cleared Users List for Datablock")
803         return {"FINISHED"}
804
805
806 class AMTH_SCENE_OT_blender_instance_open(bpy.types.Operator):
807
808     """Open in a new Blender instance"""
809     bl_idname = "scene.blender_instance_open"
810     bl_label = "Open Blender Instance"
811     filepath = bpy.props.StringProperty()
812
813     def execute(self, context):
814         if self.filepath:
815             filepath = os.path.normpath(bpy.path.abspath(self.filepath))
816
817             import subprocess
818             try:
819                 subprocess.Popen([bpy.app.binary_path, filepath])
820             except:
821                 print("Error on the new Blender instance")
822                 import traceback
823                 traceback.print_exc()
824
825         return {"FINISHED"}
826
827
828 class AMTH_SCENE_PT_scene_debug(bpy.types.Panel):
829
830     """Scene Debug"""
831     bl_label = "Scene Debug"
832     bl_space_type = "PROPERTIES"
833     bl_region_type = "WINDOW"
834     bl_context = "scene"
835
836     def draw_header(self, context):
837         layout = self.layout
838         layout.label(text="", icon="RADIO")
839
840     def draw(self, context):
841         layout = self.layout
842         scene = context.scene
843         images = bpy.data.images
844         images_missing = []
845         list_missing_images = scene.amaranth_debug_scene_list_missing_images
846         materials = AMTH_SCENE_OT_cycles_shader_list_nodes.materials
847         materials_count = len(AMTH_SCENE_OT_cycles_shader_list_nodes.materials)
848         missing_material_slots_obs = AMTH_SCENE_OT_list_missing_material_slots.objects
849         missing_material_slots_count = len(
850             AMTH_SCENE_OT_list_missing_material_slots.objects)
851         missing_material_slots_lib = AMTH_SCENE_OT_list_missing_material_slots.libraries
852         engine = scene.render.engine
853
854         # List Missing Images
855         box = layout.box()
856         row = box.row(align=True)
857         split = row.split()
858         col = split.column()
859
860         if images:
861             import os.path
862
863             for im in images:
864                 if im.type not in ("UV_TEST", "RENDER_RESULT", "COMPOSITING"):
865                     if not os.path.exists(bpy.path.abspath(im.filepath, library=im.library)):
866                         images_missing.append(["%s%s [%s]%s" % (
867                             "[L] " if im.library else "",
868                             im.name, im.users,
869                             " [F]" if im.use_fake_user else ""),
870                             im.filepath if im.filepath else "No Filepath",
871                             im.library.filepath if im.library else ""])
872
873             if images_missing:
874                 row = col.row(align=True)
875                 row.alignment = "LEFT"
876                 row.prop(
877                     scene,
878                     "amaranth_debug_scene_list_missing_images",
879                     icon="%s" %
880                     "TRIA_DOWN" if list_missing_images else "TRIA_RIGHT",
881                     emboss=False)
882
883                 split = split.split()
884                 col = split.column()
885
886                 col.label(text="%s missing %s" % (
887                           str(len(images_missing)),
888                           'image' if len(images_missing) == 1 else "images"),
889                           icon="ERROR")
890
891                 if list_missing_images:
892                     col = box.column(align=True)
893                     for mis in images_missing:
894                         row = col.row(align=True)
895                         row.alignment = "LEFT"
896                         row.label(
897                             text=mis[0],
898                             icon="IMAGE_DATA")
899                         # XXX TODO // make clicking on image work (needs new op to set x)
900                         # row.operator(
901                         #     AMTH_SCENE_OT_list_users_for_x.bl_idname,
902                         #     text=mis[0],
903                         #     icon="IMAGE_DATA",
904                         #     emboss=False).name = mis[0][:-4]
905
906                         row = col.row(align=True)
907                         row.label(text=mis[1], icon="LIBRARY_DATA_DIRECT")
908                         if mis[2]:
909                             row = col.row(align=True)
910                             row.alignment = "LEFT"
911                             row.operator(
912                                 AMTH_SCENE_OT_blender_instance_open.bl_idname,
913                                 text=mis[2],
914                                 icon="LINK_BLEND",
915                                 emboss=False).filepath = mis[2]
916                         col.separator()
917             else:
918                 row = col.row(align=True)
919                 row.alignment = "LEFT"
920                 row.label(
921                     text="Great! No missing images", icon="RIGHTARROW_THIN")
922
923                 split = split.split()
924                 col = split.column()
925
926                 col.label(text="%s %s loading correctly" % (
927                           str(len(images)),
928                           "image" if len(images) == 1 else "images"),
929                           icon="IMAGE_DATA")
930         else:
931             row = col.row(align=True)
932             row.alignment = "LEFT"
933             row.label(text="No images loaded yet", icon="RIGHTARROW_THIN")
934
935         # List Cycles Materials by Shader
936         if utils.cycles_exists() and engine == "CYCLES":
937             box = layout.box()
938             split = box.split()
939             col = split.column(align=True)
940             col.prop(scene, "amaranth_cycles_node_types",
941                      icon="MATERIAL")
942
943             row = split.row(align=True)
944             row.operator(AMTH_SCENE_OT_cycles_shader_list_nodes.bl_idname,
945                          icon="SORTSIZE",
946                          text="List Materials Using Shader")
947             if materials_count != 0:
948                 row.operator(
949                     AMTH_SCENE_OT_cycles_shader_list_nodes_clear.bl_idname,
950                     icon="X", text="")
951             col.separator()
952
953             try:
954                 materials
955             except NameError:
956                 pass
957             else:
958                 if materials_count != 0:
959                     col = box.column(align=True)
960                     count = 0
961                     col.label(
962                         text="%s %s found" %
963                         (materials_count,
964                          "material" if materials_count == 1 else "materials"),
965                         icon="INFO")
966                     for mat in materials:
967                         count += 1
968                         col.label(
969                             text="%s" %
970                             (materials[
971                                 count -
972                                 1]),
973                             icon="MATERIAL")
974
975         # List Missing Node Trees
976         box = layout.box()
977         row = box.row(align=True)
978         split = row.split()
979         col = split.column(align=True)
980
981         split = col.split()
982         split.label(text="Node Links")
983         split.operator(AMTH_SCENE_OT_list_missing_node_links.bl_idname,
984                        icon="NODETREE")
985
986         if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0 or \
987                 AMTH_SCENE_OT_list_missing_node_links.count_images != 0 or \
988                 AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
989             col.label(text="Warning! Check Console", icon="ERROR")
990
991         if AMTH_SCENE_OT_list_missing_node_links.count_groups != 0:
992             col.label(
993                 text="%s" %
994                 ("%s node %s missing link" %
995                  (str(
996                      AMTH_SCENE_OT_list_missing_node_links.count_groups),
997                      "group" if AMTH_SCENE_OT_list_missing_node_links.count_groups == 1 else "groups")),
998                 icon="NODETREE")
999         if AMTH_SCENE_OT_list_missing_node_links.count_images != 0:
1000             col.label(
1001                 text="%s" %
1002                 ("%s image %s missing link" %
1003                  (str(
1004                      AMTH_SCENE_OT_list_missing_node_links.count_images),
1005                      "node" if AMTH_SCENE_OT_list_missing_node_links.count_images == 1 else "nodes")),
1006                 icon="IMAGE_DATA")
1007
1008         if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked != 0:
1009             col.label(
1010                 text="%s" %
1011                 ("%s image %s with no output conected" %
1012                  (str(
1013                      AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked),
1014                      "node" if AMTH_SCENE_OT_list_missing_node_links.count_image_node_unlinked == 1 else "nodes")),
1015                 icon="NODE")
1016
1017         # List Empty Materials Slots
1018         box = layout.box()
1019         split = box.split()
1020         col = split.column(align=True)
1021         col.label(text="Material Slots")
1022
1023         row = split.row(align=True)
1024         row.operator(AMTH_SCENE_OT_list_missing_material_slots.bl_idname,
1025                      icon="MATERIAL",
1026                      text="List Empty Materials Slots")
1027         if missing_material_slots_count != 0:
1028             row.operator(
1029                 AMTH_SCENE_OT_list_missing_material_slots_clear.bl_idname,
1030                 icon="X", text="")
1031         col.separator()
1032
1033         try:
1034             missing_material_slots_obs
1035         except NameError:
1036             pass
1037         else:
1038             if missing_material_slots_count != 0:
1039                 col = box.column(align=True)
1040                 count = 0
1041                 count_lib = 0
1042                 col.label(
1043                     text="%s %s with empty material slots found" %
1044                     (missing_material_slots_count,
1045                      "object" if missing_material_slots_count == 1 else "objects"),
1046                     icon="INFO")
1047
1048                 for obs in missing_material_slots_obs:
1049                     count += 1
1050
1051                     row = col.row()
1052                     row.alignment = "LEFT"
1053                     row.label(
1054                         text="%s" % missing_material_slots_obs[count - 1],
1055                         icon="OBJECT_DATA")
1056
1057                 if missing_material_slots_lib:
1058                     col.separator()
1059                     col.label("Check %s:" % (
1060                         "this library" if
1061                         len(missing_material_slots_lib) == 1
1062                         else "these libraries"))
1063
1064                     for libs in missing_material_slots_lib:
1065                         count_lib += 1
1066                         row = col.row(align=True)
1067                         row.alignment = "LEFT"
1068                         row.operator(
1069                             AMTH_SCENE_OT_blender_instance_open.bl_idname,
1070                             text=missing_material_slots_lib[
1071                                 count_lib - 1],
1072                             icon="LINK_BLEND",
1073                             emboss=False).filepath = missing_material_slots_lib[
1074                             count_lib - 1]
1075
1076
1077         # List Users for Datablock
1078         list_users = AMTH_SCENE_OT_list_users_for_x.users
1079
1080         box = layout.box()
1081         row = box.row(align=True)
1082         row.label(text="List Users for Datablock")
1083
1084         col = box.column(align=True)
1085         split = col.split()
1086         row = split.row(align=True)
1087         row.prop(scene, "amth_datablock_types",
1088                     icon=scene.amth_datablock_types,
1089                     text="")
1090
1091         row.operator_menu_enum("scene.amth_list_users_for_x_type",
1092                                        "list_type_select",
1093                                        text=scene.amth_list_users_for_x_name)
1094
1095         row = split.row(align=True)
1096         row.enabled = True if scene.amth_list_users_for_x_name else False
1097         row.operator(AMTH_SCENE_OT_list_users_for_x.bl_idname).name = scene.amth_list_users_for_x_name
1098         if list_users:
1099             row.operator(
1100                 AMTH_SCENE_OT_list_users_for_x_clear.bl_idname,
1101                 icon="X", text="")
1102
1103         try:
1104             list_users
1105         except NameError:
1106             pass
1107         else:
1108             if list_users:
1109                 empty = True
1110                 col = box.column(align=True)
1111                 for t in list_users:
1112                     if list_users[t]:
1113                         empty = False
1114                         for ma in list_users[t]:
1115                             row = col.row(align=True)
1116                             row.alignment = "LEFT"
1117                             if t == 'OBJECT_DATA':
1118                                 row.operator(
1119                                     AMTH_SCENE_OT_amaranth_object_select.bl_idname,
1120                                     text="%s %s%s" %
1121                                     (" [L] " if ma.library else "",
1122                                      ma.name,
1123                                      "" if ma.name in context.scene.objects else " [Not in Scene]"),
1124                                     icon=t,
1125                                     emboss=False).object = ma.name
1126                             else:
1127                                 row.label(text=ma,
1128                                           icon=t)
1129                 if empty:
1130                     row = col.row(align=True)
1131                     row.alignment = "LEFT"
1132                     row.label(text="No users for '{0}'".format(
1133                               scene.amth_list_users_for_x_name),
1134                               icon='INFO')
1135
1136 class AMTH_LightersCorner(bpy.types.Panel):
1137
1138     """The Lighters Panel"""
1139     bl_label = "Lighter's Corner"
1140     bl_idname = "AMTH_SCENE_PT_lighters_corner"
1141     bl_space_type = 'PROPERTIES'
1142     bl_region_type = 'WINDOW'
1143     bl_context = "scene"
1144
1145     @classmethod
1146     def poll(cls, context):
1147         any_lamps = False
1148         for ob in bpy.data.objects:
1149             if ob.type == "LAMP" or utils.cycles_is_emission(context, ob):
1150                 any_lamps = True
1151             else:
1152                 pass
1153         return any_lamps
1154
1155     def draw_header(self, context):
1156         layout = self.layout
1157         layout.label(text="", icon="LAMP_SUN")
1158
1159     def draw(self, context):
1160         layout = self.layout
1161         scene = context.scene
1162         objects = bpy.data.objects
1163         ob_act = context.active_object
1164         lamps = bpy.data.lamps
1165         list_meshlights = scene.amaranth_lighterscorner_list_meshlights
1166         engine = scene.render.engine
1167
1168         if utils.cycles_exists():
1169             layout.prop(scene, "amaranth_lighterscorner_list_meshlights")
1170
1171         box = layout.box()
1172         if lamps:
1173             if objects:
1174                 row = box.row(align=True)
1175                 split = row.split(percentage=0.45)
1176                 col = split.column()
1177
1178                 col.label(text="Name")
1179
1180                 if engine in ["CYCLES", "BLENDER_RENDER"]:
1181                     if engine == "BLENDER_RENDER":
1182                         split = split.split(percentage=0.7)
1183                     else:
1184                         split = split.split(percentage=0.27)
1185                     col = split.column()
1186                     col.label(text="Samples")
1187
1188                 if utils.cycles_exists() and engine == "CYCLES":
1189                     split = split.split(percentage=0.2)
1190                     col = split.column()
1191                     col.label(text="Size")
1192
1193                 split = split.split(percentage=1.0)
1194                 col = split.column()
1195                 col.label(text="%sRender Visibility" %
1196                           "Rays /" if utils.cycles_exists() else "")
1197
1198                 for ob in objects:
1199                     is_lamp = ob.type == "LAMP"
1200                     is_emission = True if utils.cycles_is_emission(
1201                         context, ob) and list_meshlights else False
1202
1203                     if ob and is_lamp or is_emission:
1204                         lamp = ob.data
1205                         if utils.cycles_exists():
1206                             clamp = ob.data.cycles
1207                             visibility = ob.cycles_visibility
1208
1209                         row = box.row(align=True)
1210                         split = row.split(percentage=1.0)
1211                         col = split.column()
1212                         row = col.row(align=True)
1213                         col.active = ob == ob_act
1214                         row.label(
1215                             icon="%s" %
1216                             ("LAMP_%s" %
1217                              ob.data.type if is_lamp else "MESH_GRID"))
1218                         split = row.split(percentage=.45)
1219                         col = split.column()
1220                         row = col.row(align=True)
1221                         row.alignment = "LEFT"
1222                         row.active = True
1223                         row.operator(
1224                             AMTH_SCENE_OT_amaranth_object_select.bl_idname,
1225                             text="%s %s%s" %
1226                             (" [L] " if ob.library else "",
1227                              ob.name,
1228                              "" if ob.name in context.scene.objects else " [Not in Scene]"),
1229                             emboss=False).object = ob.name
1230                         if ob.library:
1231                             row = col.row(align=True)
1232                             row.alignment = "LEFT"
1233                             row.operator(
1234                                 AMTH_SCENE_OT_blender_instance_open.bl_idname,
1235                                 text=ob.library.filepath,
1236                                 icon="LINK_BLEND",
1237                                 emboss=False).filepath = ob.library.filepath
1238
1239                         if utils.cycles_exists() and engine == "CYCLES":
1240                             split = split.split(percentage=0.25)
1241                             col = split.column()
1242                             if is_lamp:
1243                                 if scene.cycles.progressive == "BRANCHED_PATH":
1244                                     col.prop(clamp, "samples", text="")
1245                                 if scene.cycles.progressive == "PATH":
1246                                     col.label(text="N/A")
1247                             else:
1248                                 col.label(text="N/A")
1249
1250                         if engine == "BLENDER_RENDER":
1251                             split = split.split(percentage=0.7)
1252                             col = split.column()
1253                             if is_lamp:
1254                                 if lamp.type == "HEMI":
1255                                     col.label(text="Not Available")
1256                                 elif lamp.type == "AREA" and lamp.shadow_method == "RAY_SHADOW":
1257                                     row = col.row(align=True)
1258                                     row.prop(
1259                                         lamp, "shadow_ray_samples_x", text="X")
1260                                     if lamp.shape == "RECTANGLE":
1261                                         row.prop(
1262                                             lamp,
1263                                             "shadow_ray_samples_y",
1264                                             text="Y")
1265                                 elif lamp.shadow_method == "RAY_SHADOW":
1266                                     col.prop(
1267                                         lamp,
1268                                         "shadow_ray_samples",
1269                                         text="Ray Samples")
1270                                 elif lamp.shadow_method == "BUFFER_SHADOW":
1271                                     col.prop(
1272                                         lamp,
1273                                         "shadow_buffer_samples",
1274                                         text="Buffer Samples")
1275                                 else:
1276                                     col.label(text="No Shadow")
1277                             else:
1278                                 col.label(text="N/A")
1279
1280                         if utils.cycles_exists() and engine == "CYCLES":
1281                             split = split.split(percentage=0.2)
1282                             col = split.column()
1283                             if is_lamp:
1284                                 if lamp.type in ["POINT", "SUN", "SPOT"]:
1285                                     col.label(
1286                                         text="%.2f" % lamp.shadow_soft_size)
1287                                 elif lamp.type == "HEMI":
1288                                     col.label(text="N/A")
1289                                 elif lamp.type == "AREA" and lamp.shape == "RECTANGLE":
1290                                     col.label(
1291                                         text="%.2fx%.2f" %
1292                                         (lamp.size, lamp.size_y))
1293                                 else:
1294                                     col.label(text="%.2f" % lamp.size)
1295                             else:
1296                                 col.label(text="N/A")
1297
1298                         split = split.split(percentage=1.0)
1299                         col = split.column()
1300                         row = col.row(align=True)
1301                         if utils.cycles_exists():
1302                             row.prop(visibility, "camera", text="")
1303                             row.prop(visibility, "diffuse", text="")
1304                             row.prop(visibility, "glossy", text="")
1305                             row.prop(visibility, "shadow", text="")
1306                             row.separator()
1307                         row.prop(ob, "hide", text="", emboss=False)
1308                         row.prop(ob, "hide_render", text="", emboss=False)
1309         else:
1310             box.label(text="No Lamps", icon="LAMP_DATA")
1311
1312
1313 classes = {
1314     AMTH_SCENE_PT_scene_debug,
1315     AMTH_SCENE_OT_blender_instance_open,
1316     AMTH_SCENE_OT_amaranth_object_select,
1317     AMTH_SCENE_OT_list_missing_node_links,
1318     AMTH_SCENE_OT_list_missing_material_slots,
1319     AMTH_SCENE_OT_list_missing_material_slots_clear,
1320     AMTH_SCENE_OT_cycles_shader_list_nodes,
1321     AMTH_SCENE_OT_cycles_shader_list_nodes_clear,
1322     AMTH_SCENE_OT_list_users_for_x,
1323     AMTH_SCENE_OT_list_users_for_x_type,
1324     AMTH_SCENE_OT_list_users_for_x_clear,
1325     AMTH_LightersCorner
1326 }
1327
1328 def register():
1329     init()
1330
1331     for cls in classes:
1332         bpy.utils.register_class(cls)
1333
1334     from bpy.types import Scene
1335
1336     bpy.types.Scene.amth_list_users_for_x_name = bpy.props.StringProperty(
1337                                                     default="",
1338                                                     name="Name",
1339                                                     description="Which datablock type to look for")
1340
1341 def unregister():
1342     clear()
1343
1344     for cls in classes:
1345         bpy.utils.unregister_class(cls)
1346
1347     from bpy.types import Scene
1348
1349     del Scene.amth_list_users_for_x_name