91 lines
2.9 KiB
Python
91 lines
2.9 KiB
Python
import bpy
|
|
|
|
from ...core import Check, Issue, Severity, contexts, is_local
|
|
|
|
# Node types that make a tree produce something.
|
|
_OUTPUT_TYPES = {
|
|
"OUTPUT_MATERIAL", "OUTPUT_WORLD", "OUTPUT_LIGHT", "OUTPUT_AOV", "OUTPUT_LINESTYLE",
|
|
"COMPOSITE", "OUTPUT_FILE", "VIEWER",
|
|
}
|
|
# Node types never reported as unused.
|
|
_IGNORED_TYPES = {"FRAME", "GROUP_INPUT"}
|
|
|
|
|
|
def unused_nodes(tree):
|
|
"""Nodes that do not contribute to any output of ``tree``.
|
|
|
|
Returns None when the tree has no output at all (nothing to compare with).
|
|
"""
|
|
roots = [
|
|
node for node in tree.nodes
|
|
if node.type in _OUTPUT_TYPES
|
|
or (node.type == "GROUP_OUTPUT" and getattr(node, "is_active_output", True))
|
|
]
|
|
if not roots:
|
|
return None
|
|
|
|
upstream = {}
|
|
for link in tree.links:
|
|
upstream.setdefault(link.to_node.name, []).append(link.from_node)
|
|
|
|
used = set()
|
|
stack = list(roots)
|
|
while stack:
|
|
node = stack.pop()
|
|
if node.name in used:
|
|
continue
|
|
used.add(node.name)
|
|
stack.extend(upstream.get(node.name, ()))
|
|
|
|
return [node for node in tree.nodes if node.name not in used and node.type not in _IGNORED_TYPES]
|
|
|
|
|
|
def _node_trees():
|
|
"""(owner datablock, node tree) of every local node tree in use."""
|
|
for material in bpy.data.materials:
|
|
if is_local(material) and material.users and material.node_tree:
|
|
yield material, material.node_tree
|
|
for world in bpy.data.worlds:
|
|
if is_local(world) and world.users and world.node_tree:
|
|
yield world, world.node_tree
|
|
for group in bpy.data.node_groups:
|
|
if is_local(group) and group.users:
|
|
yield group, group
|
|
|
|
|
|
def _tree_of(owner):
|
|
return owner if isinstance(owner, bpy.types.NodeTree) else owner.node_tree
|
|
|
|
|
|
class UnusedNodesCheck(Check):
|
|
id = "unused_nodes"
|
|
label = "Unused nodes"
|
|
description = "Material, world and node group trees contain no node disconnected from the output."
|
|
category = "Shading"
|
|
contexts = contexts.ASSET
|
|
severity = Severity.WARNING
|
|
fix_description = "Delete the unused nodes"
|
|
|
|
def run(self, ctx):
|
|
for owner, tree in _node_trees():
|
|
nodes = unused_nodes(tree)
|
|
if nodes:
|
|
names = [node.name for node in nodes]
|
|
shown = ", ".join(names[:5]) + (" ..." if len(names) > 5 else "")
|
|
yield Issue.for_id(
|
|
f"{owner.name}: {len(names)} unused node(s): {shown}",
|
|
owner,
|
|
data={"nodes": names},
|
|
)
|
|
|
|
def fix(self, ctx, issues):
|
|
for issue in issues:
|
|
owner = issue.get_id()
|
|
if owner is None:
|
|
continue
|
|
tree = _tree_of(owner)
|
|
for name in issue.data["nodes"]:
|
|
node = tree.nodes.get(name)
|
|
if node is not None:
|
|
tree.nodes.remove(node)
|