Implement arrangement framework for node groups

- Introduced `frames.py` to manage field complex calculations and their arrangement within node groups.
- Added functionality to create and remove field frames based on node arrangements.
- Developed `passages.py` to find unobstructed paths for connections around calculation frames.
- Implemented `references.py` to route shared results on peripheral tracks, ensuring efficient distribution of outputs.
- Created `sections.py` to reserve space for field calculations adjacent to geometry consumers.
- Added `sources.py` to position shared calculation sources effectively beside their distribution channels.
- Enhanced outline smoothing with `outline_smoothing.py` to adapt camera depth along original outlines.
This commit is contained in:
2026-09-12 12:19:07 +08:00
parent dd32ccdc44
commit 5b2dc59f79
57 changed files with 2253 additions and 700 deletions

View File

@@ -155,15 +155,23 @@ def sample_face_camera(group, image):
return texture.outputs["Color"]
def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
face_center=None, triangles=False):
"""Split adjacent face samples using their Z depth and base center spacing."""
def ray_distance(group, camera):
"""Measure the distance from the camera origin to a camera-space point."""
length = group.nodes.new("ShaderNodeVectorMath")
length.operation = "LENGTH"
group.links.new(camera, length.inputs[0])
return length.outputs["Value"]
def split_depth_surface(group, geometry, face_camera, strength, reference_depth, *,
face_distance=None, face_center=None, triangles=False):
"""Split relative ray-distance jumps over the base face-center spacing."""
nodes, links = group.nodes, group.links
if face_center is None:
face_center = nodes.new("GeometryNodeInputPosition").outputs[0]
face_camera = evaluate_field(group, face_camera, "FLOAT_VECTOR", "FACE")
axes = nodes.new("ShaderNodeSeparateXYZ")
links.new(face_camera, axes.inputs[0])
if face_distance is None:
face_distance = ray_distance(group, face_camera)
centers, depths = [], []
for side in (0, 1):
corner = nodes.new("GeometryNodeCornersOfEdge")
@@ -172,7 +180,7 @@ def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
links.new(corner.outputs["Corner Index"], face.inputs["Corner Index"])
for data_type, field, results in (
("FLOAT_VECTOR", face_center, centers),
("FLOAT", axes.outputs["Z"], depths),
("FLOAT", face_distance, depths),
):
sample = nodes.new("GeometryNodeFieldAtIndex")
sample.data_type = data_type
@@ -185,13 +193,15 @@ def split_depth_surface(group, geometry, face_camera, strength, uniform, *,
for index, center in enumerate(centers):
links.new(center, distance.inputs[index])
delta = _math(group, "ABSOLUTE", _math(group, "SUBTRACT", *depths))
scaled = _math(group, "MULTIPLY", delta, uniform)
# Cross multiplication avoids division and stays invariant under uniform scaling.
local_distance = _math(group, "MAXIMUM", _math(group, "MULTIPLY", _math(group, "ADD", *depths), 0.5), 1e-8)
reference = _math(group, "MAXIMUM", reference_depth, 1e-8)
# Uniform Scale cancels between the distance jump and local spacing correction.
# Cross multiplication also avoids dividing by the local distance.
selected = _math(
group,
"GREATER_THAN",
_math(group, "MULTIPLY", scaled, strength),
_math(group, "MULTIPLY", distance.outputs["Value"], 2.5),
_math(group, "MULTIPLY", _math(group, "MULTIPLY", delta, reference), strength),
_math(group, "MULTIPLY", _math(group, "MULTIPLY", distance.outputs["Value"], local_distance), 2.5),
)
selected = _math(
group,
@@ -352,7 +362,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
normal = nodes.new("GeometryNodeInputNormal")
blur = nodes.new("GeometryNodeBlurAttribute")
blur.data_type = "FLOAT_VECTOR"
links.new(normal_smooth, blur.inputs["Iterations"])
blur.inputs["Iterations"].default_value = normal_smooth
links.new(normal.outputs[0], blur.inputs["Value"])
normalize = nodes.new("ShaderNodeVectorMath")
normalize.operation = "NORMALIZE"