Files
AnyImage/tools/nodes/arrangement/branches.py
nan dc000e50b1 feat: Implement weight cutout boundary smoothing for image depth cutout
- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements.
- Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values.
- Updated specifications to define the behavior of boundary-specific smoothing strength and default settings.
- Created tasks to verify the implementation of boundary behavior and asset updates.
- Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance.
- Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials.
- Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
2026-09-14 12:44:20 +08:00

81 lines
3.2 KiB
Python

"""Identify connected calculations by their nearest geometry or shader consumers."""
from collections import defaultdict
from graphlib import TopologicalSorter
def calculation_blocks(nodes, edges):
"""Group fields with equal consumers, then attach their local interface reads."""
ids = {node: i for i, node in enumerate(nodes)}
parents = {i: set() for i in range(len(nodes))}
children = {i: set() for i in parents}
for edge in edges:
parents[ids[edge.to_node]].add(ids[edge.from_node])
children[ids[edge.from_node]].add(ids[edge.to_node])
order = list(TopologicalSorter(parents).static_order())
core = {
i
for i, n in enumerate(nodes)
if n.bl_idname == "NodeGroupOutput"
or n.bl_idname in {"ShaderNodeNormalMap", "ShaderNodeBump", "ShaderNodeDisplacement"}
or any(s.type in {"GEOMETRY", "SHADER"} and s.is_linked for s in n.outputs)
or getattr(n, "paired_output", None) is not None
}
inputs = {i for i, n in enumerate(nodes) if n.bl_idname == "NodeGroupInput"}
core -= inputs
fields = set(range(len(nodes))) - core - inputs
destinations = {}
for i in reversed(order):
destinations[i] = frozenset().union(
*(frozenset([j]) if j in core else destinations[j] for j in children[i])
)
groups = defaultdict(set)
for i in fields:
groups[destinations[i]].add(i)
blocks = []
for targets, members in groups.items():
pending = set(members)
while pending:
component = set()
queue = [min(pending)]
while queue:
i = queue.pop()
if i not in pending:
continue
pending.remove(i)
component.add(i)
queue.extend((parents[i] | children[i]) & pending)
target = min(targets) if targets else None
ports = list(
dict.fromkeys(
e.to_socket.name
for e in edges
if ids[e.from_node] in component and ids[e.to_node] == target
)
)
if ports:
title = nodes[target].name + " · " + ", ".join(ports)
else:
outlets = [i for i in component if children[i] - component]
source = (
max(outlets, key=lambda i: (len(children[i] - component), i))
if outlets
else max(component)
)
title = "共享 · " + nodes[source].name + " → " + str(len(targets)) + " 处计算"
blocks.append(dict(title=title, members=sorted(component), targets=sorted(targets)))
block_of = {i: k for k, b in enumerate(blocks) for i in b["members"]}
# Keep one local original read; additional consumers receive local copies later.
for i in sorted(inputs):
owners = [block_of[j] for j in sorted(children[i]) if j in block_of]
if any(j in core for j in children[i]):
core.add(i)
elif owners:
k = owners[0]
blocks[k]["members"].append(i)
block_of[i] = k
else:
core.add(i)
return blocks, core