Added sanity check baseline made by claude.

Also changed the location to a panel in VIEW_3D instead of menu.
This commit is contained in:
Nero Frame 13 committed 2026-10-13 16:33:03 +02:00
1 parent 71b1a61b5c
commit 4617c47ad0
33 files changed
+2771 -52

No files matched your search

+90
View File
@@ -0,0 +1,90 @@
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)