""" Copyright (C) 2019 Remington Creative This file is part of Atomic Data Manager. Atomic Data Manager is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Atomic Data Manager is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Atomic Data Manager. If not, see . --- This file contains functions that return the keys of data-blocks that use other data-blocks. They are titled as such that the first part of the function name is the type of the data being passed in and the second part of the function name is the users of that type. e.g. If you were searching for all of the places where an image is used in a material would be searching for the image_materials() function. """ import bpy def collection_all(collection_key): # returns a list of keys of every data-block that uses this collection return collection_cameras(collection_key) + \ collection_children(collection_key) + \ collection_lights(collection_key) + \ collection_meshes(collection_key) + \ collection_others(collection_key) + \ collection_rigidbody_world(collection_key) + \ collection_scenes(collection_key) def collection_cameras(collection_key): # recursively returns a list of camera object keys that are in the # collection and its child collections users = [] collection = bpy.data.collections[collection_key] # append all camera objects in our collection for obj in collection.objects: if obj.type == 'CAMERA': users.append(obj.name) # list of all child collections in our collection children = collection_children(collection_key) # append all camera objects from the child collections for child in children: for obj in bpy.data.collections[child].objects: if obj.type == 'CAMERA': users.append(obj.name) return distinct(users) def collection_children(collection_key): # returns a list of all child collections under the specified # collection using recursive functions collection = bpy.data.collections[collection_key] children = collection_children_recursive(collection_key) children.remove(collection.name) return children def collection_children_recursive(collection_key): # recursively returns a list of all child collections under the # specified collection including the collection itself collection = bpy.data.collections[collection_key] # base case if not collection.children: return [collection.name] # recursion case else: children = [] for child in collection.children: children += collection_children(child.name) children.append(collection.name) return children def collection_lights(collection_key): # returns a list of light object keys that are in the collection users = [] collection = bpy.data.collections[collection_key] # append all light objects in our collection for obj in collection.objects: if obj.type == 'LIGHT': users.append(obj.name) # list of all child collections in our collection children = collection_children(collection_key) # append all light objects from the child collections for child in children: for obj in bpy.data.collections[child].objects: if obj.type == 'LIGHT': users.append(obj.name) return distinct(users) def collection_meshes(collection_key): # returns a list of mesh object keys that are in the collection users = [] collection = bpy.data.collections[collection_key] # append all mesh objects in our collection and from child # collections for obj in collection.all_objects: if obj.type == 'MESH': users.append(obj.name) return distinct(users) def collection_others(collection_key): # returns a list of other object keys that are in the collection # NOTE: excludes cameras, lights, and meshes users = [] collection = bpy.data.collections[collection_key] # object types to exclude from this search excluded_types = ['CAMERA', 'LIGHT', 'MESH'] # append all other objects in our collection and from child # collections for obj in collection.all_objects: if obj.type not in excluded_types: users.append(obj.name) return distinct(users) def collection_rigidbody_world(collection_key): # returns a list containing "RigidBodyWorld" if the collection is used # by any scene's rigidbody_world.collection users = [] collection = bpy.data.collections[collection_key] # check all scenes for rigidbody_world usage for scene in bpy.data.scenes: # check if scene has rigidbody_world and if it uses our collection if hasattr(scene, 'rigidbody_world') and scene.rigidbody_world: if hasattr(scene.rigidbody_world, 'collection') and scene.rigidbody_world.collection: if scene.rigidbody_world.collection.name == collection.name: users.append("RigidBodyWorld") return distinct(users) def collection_scenes(collection_key): # returns a list of scene names that include this collection anywhere in # their collection hierarchy users = [] collection = bpy.data.collections[collection_key] for scene in bpy.data.scenes: if _scene_collection_contains(scene.collection, collection): users.append(scene.name) return distinct(users) def _scene_collection_contains(parent_collection, target_collection): # helper that checks whether target_collection exists inside the # parent_collection hierarchy if parent_collection.name == target_collection.name: return True for child in parent_collection.children: if _scene_collection_contains(child, target_collection): return True return False def image_all(image_key): # returns a list of keys of every data-block that uses this image return image_compositors(image_key) + \ image_materials(image_key) + \ image_node_groups(image_key) + \ image_textures(image_key) + \ image_worlds(image_key) + \ image_geometry_nodes(image_key) def image_compositors(image_key): # returns a list containing "Compositor" if the image is used in # the scene's compositor users = [] image = bpy.data.images[image_key] # a list of node groups that use our image node_group_users = image_node_groups(image_key) # Import compat module for version-safe compositor access from ..utils import compat # if our compositor uses nodes and has a valid node tree scene = bpy.context.scene if scene.use_nodes: node_tree = compat.get_scene_compositor_node_tree(scene) if node_tree: # check each node in the compositor for node in node_tree.nodes: # if the node is an image node with a valid image if hasattr(node, 'image') and node.image: # if the node's image is our image if node.image.name == image.name: users.append("Compositor") # if the node is a group node with a valid node tree elif hasattr(node, 'node_tree') and node.node_tree: # if the node tree's name is in our list of node group # users if node.node_tree.name in node_group_users: users.append("Compositor") return distinct(users) def image_materials(image_key): # returns a list of material keys that use the image users = [] image = bpy.data.images[image_key] # list of node groups that use this image node_group_users = image_node_groups(image_key) for mat in bpy.data.materials: # if material uses a valid node tree, check each node if mat.use_nodes and mat.node_tree: for node in mat.node_tree.nodes: # if node is has a not none image attribute if hasattr(node, 'image') and node.image: # if the nodes image is our image if node.image.name == image.name: users.append(mat.name) # if image in node in node group in node tree elif node.type == 'GROUP': # if node group has a valid node tree and is in our # list of node groups that use this image if node.node_tree and \ node.node_tree.name in node_group_users: users.append(mat.name) return distinct(users) def image_node_groups(image_key): # returns a list of keys of node groups that use this image users = [] image = bpy.data.images[image_key] # for each node group for node_group in bpy.data.node_groups: # if node group contains our image if node_group_has_image(node_group.name, image.name): users.append(node_group.name) return distinct(users) def image_textures(image_key): # returns a list of texture keys that use the image if not hasattr(bpy.data, 'textures'): return [] users = [] image = bpy.data.images[image_key] # list of node groups that use this image node_group_users = image_node_groups(image_key) for texture in bpy.data.textures: # if texture uses a valid node tree, check each node if texture.use_nodes and texture.node_tree: for node in texture.node_tree.nodes: # check image nodes that use this image if hasattr(node, 'image') and node.image: if node.image.name == image.name: users.append(texture.name) # check for node groups that use this image elif hasattr(node, 'node_tree') and node.node_tree: # if node group is in our list of node groups that # use this image if node.node_tree.name in node_group_users: users.append(texture.name) # otherwise check the texture's image attribute else: # if texture uses an image if hasattr(texture, 'image') and texture.image: # if texture image is our image if texture.image.name == image.name: users.append(texture.name) return distinct(users) def image_geometry_nodes(image_key): # returns a list of object keys that use the image through Geometry Nodes users = [] image = bpy.data.images[image_key] # list of node groups that use this image node_group_users = image_node_groups(image_key) # Import compat module for version-safe geometry nodes access from ..utils import compat for obj in bpy.data.objects: # check Geometry Nodes modifiers if hasattr(obj, 'modifiers'): for modifier in obj.modifiers: if compat.is_geometry_nodes_modifier(modifier): ng = compat.get_geometry_nodes_modifier_node_group(modifier) if ng: # direct usage in the modifier's tree if node_group_has_image(ng.name, image.name): users.append(obj.name) # usage via nested node groups elif ng.name in node_group_users: users.append(obj.name) return distinct(users) def image_worlds(image_key): # returns a list of world keys that use the image users = [] image = bpy.data.images[image_key] # list of node groups that use this image node_group_users = image_node_groups(image_key) for world in bpy.data.worlds: # if world uses a valid node tree, check each node if world.use_nodes and world.node_tree: for node in world.node_tree.nodes: # check image nodes if hasattr(node, 'image') and node.image: if node.image.name == image.name: users.append(world.name) # check for node groups that use this image elif hasattr(node, 'node_tree') and node.node_tree: if node.node_tree.name in node_group_users: users.append(world.name) return distinct(users) def light_all(light_key): # returns a list of keys of every data-block that uses this light return light_objects(light_key) def light_objects(light_key): # returns a list of light object keys that use the light data users = [] light = bpy.data.lights[light_key] for obj in bpy.data.objects: if obj.type == 'LIGHT' and obj.data: if obj.data.name == light.name: users.append(obj.name) return distinct(users) def material_all(material_key): # returns a list of keys of every data-block that uses this material return material_objects(material_key) + \ material_geometry_nodes(material_key) def material_geometry_nodes(material_key): # returns a list of object keys that use the material via Geometry Nodes users = [] material = bpy.data.materials[material_key] # Import compat module for version-safe geometry nodes access from ..utils import compat for obj in bpy.data.objects: if hasattr(obj, 'modifiers'): for modifier in obj.modifiers: if compat.is_geometry_nodes_modifier(modifier): ng = compat.get_geometry_nodes_modifier_node_group(modifier) if ng: if node_group_has_material(ng.name, material.name): users.append(obj.name) return distinct(users) def material_objects(material_key): # returns a list of object keys that use this material users = [] material = bpy.data.materials[material_key] for obj in bpy.data.objects: # if the object has the option to add materials if hasattr(obj, 'material_slots'): # for each material slot for slot in obj.material_slots: # if material slot has a valid material and it is our # material if slot.material and slot.material.name == material.name: users.append(obj.name) return distinct(users) def node_group_all(node_group_key): # returns a list of keys of every data-block that uses this node group return node_group_compositors(node_group_key) + \ node_group_materials(node_group_key) + \ node_group_node_groups(node_group_key) + \ node_group_textures(node_group_key) + \ node_group_worlds(node_group_key) + \ node_group_objects(node_group_key) def node_group_compositors(node_group_key): # returns a list containing "Compositor" if the node group is used in # the scene's compositor users = [] node_group = bpy.data.node_groups[node_group_key] # a list of node groups that use our node group node_group_users = node_group_node_groups(node_group_key) # Import compat module for version-safe compositor access from ..utils import compat # if our compositor uses nodes and has a valid node tree scene = bpy.context.scene if scene.use_nodes: node_tree = compat.get_scene_compositor_node_tree(scene) if node_tree: # check each node in the compositor for node in node_tree.nodes: # if the node is a group and has a valid node tree if hasattr(node, 'node_tree') and node.node_tree: # if the node group is our node group if node.node_tree.name == node_group.name: users.append("Compositor") # if the node group is in our list of node group users if node.node_tree.name in node_group_users: users.append("Compositor") return distinct(users) def node_group_materials(node_group_key): # returns a list of material keys that use the node group in their # node trees users = [] node_group = bpy.data.node_groups[node_group_key] # node groups that use this node group node_group_users = node_group_node_groups(node_group_key) for material in bpy.data.materials: # if material uses nodes and has a valid node tree, check each node if material.use_nodes and material.node_tree: for node in material.node_tree.nodes: # if node is a group node if hasattr(node, 'node_tree') and node.node_tree: # if node is the node group if node.node_tree.name == node_group.name: users.append(material.name) # if node is using a node group contains our node group if node.node_tree.name in node_group_users: users.append(material.name) return distinct(users) def node_group_node_groups(node_group_key): # returns a list of all node groups that use this node group in # their node tree users = [] node_group = bpy.data.node_groups[node_group_key] # for each search group for search_group in bpy.data.node_groups: # if the search group contains our node group if node_group_has_node_group( search_group.name, node_group.name): users.append(search_group.name) return distinct(users) def node_group_textures(node_group_key): # returns a list of texture keys that use this node group in their # node trees if not hasattr(bpy.data, 'textures'): return [] users = [] node_group = bpy.data.node_groups[node_group_key] # list of node groups that use this node group node_group_users = node_group_node_groups(node_group_key) for texture in bpy.data.textures: # if texture uses a valid node tree, check each node if texture.use_nodes and texture.node_tree: for node in texture.node_tree.nodes: # check if node is a node group and has a valid node tree if hasattr(node, 'node_tree') and node.node_tree: # if node is our node group if node.node_tree.name == node_group.name: users.append(texture.name) # if node is a node group that contains our node group if node.node_tree.name in node_group_users: users.append(texture.name) return distinct(users) def node_group_worlds(node_group_key): # returns a list of world keys that use the node group in their node # trees users = [] node_group = bpy.data.node_groups[node_group_key] # node groups that use this node group node_group_users = node_group_node_groups(node_group_key) for world in bpy.data.worlds: # if world uses nodes and has a valid node tree if world.use_nodes and world.node_tree: for node in world.node_tree.nodes: # if node is a node group and has a valid node tree if hasattr(node, 'node_tree') and node.node_tree: # if this node is our node group if node.node_tree.name == node_group.name: users.append(world.name) # if this node is one of the node groups that use # our node group elif node.node_tree.name in node_group_users: users.append(world.name) return distinct(users) def node_group_objects(node_group_key): # returns a list of object keys that use this node group via Geometry Nodes modifiers users = [] node_group = bpy.data.node_groups[node_group_key] # node groups that use this node group node_group_users = node_group_node_groups(node_group_key) # Import compat module for version-safe geometry nodes access from ..utils import compat for obj in bpy.data.objects: if hasattr(obj, 'modifiers'): for modifier in obj.modifiers: if compat.is_geometry_nodes_modifier(modifier): ng = compat.get_geometry_nodes_modifier_node_group(modifier) if ng: if ng.name == node_group.name or ng.name in node_group_users: users.append(obj.name) return distinct(users) def node_group_has_image(node_group_key, image_key): # recursively returns true if the node group contains this image # directly or if it contains a node group a node group that contains # the image indirectly has_image = False node_group = bpy.data.node_groups[node_group_key] image = bpy.data.images[image_key] # for each node in our search group for node in node_group.nodes: # base case # if node has a not none image attribute if hasattr(node, 'image') and node.image: # if the node group is our node group if node.image.name == image.name: has_image = True # recurse case # if node is a node group and has a valid node tree elif hasattr(node, 'node_tree') and node.node_tree: has_image = node_group_has_image( node.node_tree.name, image.name) # break the loop if the image is found if has_image: break return has_image def node_group_has_node_group(search_group_key, node_group_key): # returns true if a node group contains this node group has_node_group = False search_group = bpy.data.node_groups[search_group_key] node_group = bpy.data.node_groups[node_group_key] # for each node in our search group for node in search_group.nodes: # if node is a node group and has a valid node tree if hasattr(node, 'node_tree') and node.node_tree: if node.node_tree.name == "RG_MetallicMap": print(node.node_tree.name) print(node_group.name) # base case # if node group is our node group if node.node_tree.name == node_group.name: has_node_group = True # recurse case # if node group is any other node group else: has_node_group = node_group_has_node_group( node.node_tree.name, node_group.name) # break the loop if the node group is found if has_node_group: break return has_node_group def node_group_has_texture(node_group_key, texture_key): # returns true if a node group contains this image has_texture = False if not hasattr(bpy.data, 'textures'): return has_texture node_group = bpy.data.node_groups[node_group_key] texture = bpy.data.textures[texture_key] # for each node in our search group for node in node_group.nodes: # base case # if node has a not none image attribute if hasattr(node, 'texture') and node.texture: # if the node group is our node group if node.texture.name == texture.name: has_texture = True # recurse case # if node is a node group and has a valid node tree elif hasattr(node, 'node_tree') and node.node_tree: has_texture = node_group_has_texture( node.node_tree.name, texture.name) # break the loop if the texture is found if has_texture: break return has_texture def node_group_has_material(node_group_key, material_key): # returns true if a node group contains this material (directly or nested) has_material = False node_group = bpy.data.node_groups[node_group_key] material = bpy.data.materials[material_key] for node in node_group.nodes: # base case: nodes with a material property (e.g., Set Material) if hasattr(node, 'material') and node.material: if node.material.name == material.name: has_material = True # recurse case: nested node groups elif hasattr(node, 'node_tree') and node.node_tree: has_material = node_group_has_material( node.node_tree.name, material.name) if has_material: break return has_material def particle_all(particle_key): # returns a list of keys of every data-block that uses this particle # system return particle_objects(particle_key) def particle_objects(particle_key): # returns a list of object keys that use the particle system if not hasattr(bpy.data, 'particles'): return [] users = [] particle_system = bpy.data.particles[particle_key] for obj in bpy.data.objects: # if object can have a particle system if hasattr(obj, 'particle_systems'): for particle in obj.particle_systems: # if particle settings is our particle system if particle.settings.name == particle_system.name: users.append(obj.name) return distinct(users) def texture_all(texture_key): # returns a list of keys of every data-block that uses this texture return texture_brushes(texture_key) + \ texture_compositor(texture_key) + \ texture_objects(texture_key) + \ texture_node_groups(texture_key) + \ texture_particles(texture_key) def texture_brushes(texture_key): # returns a list of brush keys that use the texture if not hasattr(bpy.data, 'textures'): return [] users = [] texture = bpy.data.textures[texture_key] for brush in bpy.data.brushes: # if brush has a texture if brush.texture: # if brush texture is our texture if brush.texture.name == texture.name: users.append(brush.name) return distinct(users) def texture_compositor(texture_key): # returns a list containing "Compositor" if the texture is used in # the scene's compositor if not hasattr(bpy.data, 'textures'): return [] users = [] texture = bpy.data.textures[texture_key] # a list of node groups that use our image node_group_users = texture_node_groups(texture_key) # Import compat module for version-safe compositor access from ..utils import compat # if our compositor uses nodes and has a valid node tree scene = bpy.context.scene if scene.use_nodes: node_tree = compat.get_scene_compositor_node_tree(scene) if node_tree: # check each node in the compositor for node in node_tree.nodes: # if the node is an texture node with a valid texture if hasattr(node, 'texture') and node.texture: # if the node's texture is our texture if node.texture.name == texture.name: users.append("Compositor") # if the node is a group node with a valid node tree elif hasattr(node, 'node_tree') and node.node_tree: # if the node tree's name is in our list of node group # users if node.node_tree.name in node_group_users: users.append("Compositor") return distinct(users) def texture_objects(texture_key): # returns a list of object keys that use the texture in one of their # modifiers if not hasattr(bpy.data, 'textures'): return [] users = [] texture = bpy.data.textures[texture_key] # list of particle systems that use our texture particle_users = texture_particles(texture_key) # append objects that use the texture in a modifier for obj in bpy.data.objects: # if object can have modifiers applied to it if hasattr(obj, 'modifiers'): for modifier in obj.modifiers: # if the modifier has a texture attribute that is not None if hasattr(modifier, 'texture') \ and modifier.texture: if modifier.texture.name == texture.name: users.append(obj.name) # if the modifier has a mask_texture attribute that is # not None elif hasattr(modifier, 'mask_texture') \ and modifier.mask_texture: if modifier.mask_texture.name == texture.name: users.append(obj.name) # append objects that use the texture in a particle system for particle in particle_users: # append all objects that use the particle system users += particle_objects(particle) return distinct(users) def texture_node_groups(texture_key): # returns a list of keys of all node groups that use this texture if not hasattr(bpy.data, 'textures'): return [] users = [] texture = bpy.data.textures[texture_key] # for each node group for node_group in bpy.data.node_groups: # if node group contains our texture if node_group_has_texture( node_group.name, texture.name): users.append(node_group.name) return distinct(users) def texture_particles(texture_key): # returns a list of particle system keys that use the texture in # their texture slots if not hasattr(bpy.data, 'textures') or not hasattr(bpy.data, 'particles'): return [] users = [] texture = bpy.data.textures[texture_key] for particle in bpy.data.particles: # for each texture slot in the particle system for texture_slot in particle.texture_slots: # if texture slot has a texture that is not None if hasattr(texture_slot, 'texture') and texture_slot.texture: # if texture in texture slot is our texture if texture_slot.texture.name == texture.name: users.append(particle.name) return distinct(users) def distinct(seq): # returns a list of distinct elements return list(set(seq))