"""Place independent Cutout stages using authored local layouts.""" from collections import Counter def route_geometry(group, source, positions): """Guide a shared forward geometry reference through authored waypoints.""" for position in positions: reroute = group.nodes.new("NodeReroute") reroute.location = position group.links.new(source, reroute.inputs[0]) source = reroute.outputs[0] return source def layout_stage(group, previous, positions, input_regions, offset=(0, 0)): """Map stage-local default names without renaming Blender nodes.""" counts, stage = Counter(), {} for node in group.nodes: if node in previous or node.bl_idname == "NodeGroupInput": continue base, dot, suffix = node.name.rpartition(".") base = base if dot and suffix.isdigit() else node.name index = counts[base] counts[base] += 1 stage[base if index == 0 else f"{base}.{index:03d}"] = node assert set(stage) == set(positions) - {"Set Shade Smooth", "Group Output"}, (group.name, set(stage) ^ set(positions)) for name, node in stage.items(): x, y = positions[name] node.location = (x + offset[0], y + offset[1]) inputs = [(link.to_socket, link.from_socket.name) for link in group.links if link.to_node in stage.values() and link.from_node.bl_idname == "NodeGroupInput"] for targets, (x, y) in input_regions: sockets = [(socket, name) for socket, name in inputs if socket.node in [stage[key] for key in targets if key in stage]] if not sockets: continue control = group.nodes.new("NodeGroupInput") control.location = (x + offset[0], y + offset[1]) for socket, name in sockets: group.links.new(control.outputs[name], socket) return stage def finish_layout(group): """Expose linked fields and retain readable constants.""" for node in group.nodes: node.select = False node.width = 200 if "Attribute" in node.bl_idname else 160 if node.bl_idname in {"ShaderNodeMath", "ShaderNodeVectorMath"}: node.hide = all(socket.is_linked for socket in node.inputs if socket.enabled and not socket.hide) for socket in node.outputs: socket.hide = not socket.is_linked for node in list(group.nodes): if node.bl_idname == "NodeGroupInput" and not any(socket.is_linked for socket in node.outputs): group.nodes.remove(node)