refactor: Update cutout and depth handling by removing unused functions and adjusting parameters

This commit is contained in:
2026-09-13 22:08:32 +08:00
parent a0dd6e4a88
commit 1bf858b17e
13 changed files with 88 additions and 326 deletions

View File

@@ -20,8 +20,8 @@ def remove_boundary_triangles(group, geometry):
return delete.outputs["Geometry"]
def taper_split_profile(group, original, geometry):
"""Taper balloon thickness through the shared two-ring influence builder."""
def original_boundary_field(group, original):
"""Sample the unsplit outline in UV space after projection and separation."""
nodes, links = group.nodes, group.links
uv = nodes.new("GeometryNodeInputNamedAttribute")
uv.data_type = "FLOAT_VECTOR"
@@ -34,8 +34,13 @@ def taper_split_profile(group, original, geometry):
links.new(original_uv.outputs["Geometry"], nearest.inputs["Geometry"])
links.new(uv.outputs["Attribute"], nearest.inputs["Sample Position"])
rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
protected = sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"])
influence = boundary_influence(group, protected)
return sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"])
def taper_split_profile(group, geometry, original_boundary):
"""Taper balloon thickness through the shared two-ring influence builder."""
nodes = group.nodes
influence = boundary_influence(group, original_boundary)
controls = group_input(nodes, {"Depth Split"})
band = _math(group, "MULTIPLY", influence, _math(group, "GREATER_THAN", controls.outputs["Depth Split"], 0))
retained = _math(group, "SQRT", _math(group, "MAXIMUM", _math(group, "SUBTRACT", 1, band), 0))

View File

@@ -1,78 +0,0 @@
"""Build a shared center on the original Cutout mesh."""
from .nodes import evaluate_field, group_input, sample_field
def vector(group, operation, first, second=None):
node = group.nodes.new("ShaderNodeVectorMath")
node.operation = operation
for value, socket in ((first, node.inputs[0]), (second, node.inputs[3 if operation == "SCALE" else 1])):
if value is None:
continue
if hasattr(value, "node"):
group.links.new(value, socket)
else:
socket.default_value = value
return node.outputs[0]
def axes(group, value):
node = group.nodes.new("ShaderNodeSeparateXYZ")
group.links.new(value, node.inputs[0])
return node.outputs
def combine(group, x, y, z=0):
node = group.nodes.new("ShaderNodeCombineXYZ")
for value, socket in zip((x, y, z), node.inputs):
if hasattr(value, "node"):
group.links.new(value, socket)
else:
socket.default_value = value
return node.outputs[0]
def set_position(group, geometry, position):
node = group.nodes.new("GeometryNodeSetPosition")
group.links.new(geometry, node.inputs["Geometry"])
group.links.new(position, node.inputs["Position"])
return node.outputs[0]
def sample_surface(group, geometry, value, data_type, domain_position, query):
"""Keep point indices on points before the face interpolation adapts domains."""
field = evaluate_field(group, sample_field(group, geometry, value, data_type), data_type, "POINT")
flat = set_position(group, geometry, domain_position)
sample = group.nodes.new("GeometryNodeSampleNearestSurface")
sample.data_type = data_type
group.links.new(flat, sample.inputs["Mesh"])
group.links.new(field, sample.inputs["Value"])
group.links.new(query, sample.inputs["Sample Position"])
return sample.outputs[0]
def build_center_surface(group, unsplit):
"""Smooth the unsplit displaced surface without moving its XY coordinates."""
nodes, links = group.nodes, group.links
controls = group_input(nodes, {"Center Smooth"})
position = nodes.new("GeometryNodeInputPosition").outputs[0]
components = axes(group, position)
uv = nodes.new("GeometryNodeInputNamedAttribute")
uv.data_type = "FLOAT_VECTOR"
uv.inputs["Name"].default_value = "UVMap"
coordinates = uv.outputs["Attribute"]
blur = nodes.new("GeometryNodeBlurAttribute")
blur.data_type = "FLOAT"
links.new(components["Z"], blur.inputs["Value"])
links.new(controls.outputs["Center Smooth"], blur.inputs["Iterations"])
support = set_position(group, unsplit, combine(group, components["X"], components["Y"], blur.outputs[0]))
height = sample_surface(group, support, components["Z"], "FLOAT", coordinates, coordinates)
normal = nodes.new("GeometryNodeInputNormal").outputs[0]
normal_blur = nodes.new("GeometryNodeBlurAttribute")
normal_blur.data_type = "FLOAT_VECTOR"
links.new(evaluate_field(group, normal, "FLOAT_VECTOR", "POINT"), normal_blur.inputs["Value"])
links.new(controls.outputs["Center Smooth"], normal_blur.inputs["Iterations"])
normal = normal_blur.outputs[0]
normal = vector(group, "NORMALIZE", sample_surface(group, support, normal, "FLOAT_VECTOR", coordinates, coordinates))
return {"geometry": support, "preview": support, "height": height, "normal": normal}

View File

@@ -1,98 +0,0 @@
"""Adapt camera depth along the original outline with batched weight updates."""
from .boundary_smoothing import boundary_influence, reconstruct_camera_depth
from .depth_surface import CUT_ATTRIBUTE, _math, ray_distance
from .nodes import evaluate_field, group_input, read_float_attribute, sample_field
from .smoothing import edge_boundary_field
def _outline_influence(group, original, geometry):
"""Identify original outline points and protect split boundary intersections."""
nodes, links = group.nodes, group.links
rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
nearest = nodes.new("GeometryNodeSampleNearest")
nearest.domain = "POINT"
links.new(original, nearest.inputs["Geometry"])
position = nodes.new("GeometryNodeInputPosition").outputs["Position"]
links.new(position, nearest.inputs["Sample Position"])
original_rim = sample_field(group, original, rim, "FLOAT", index=nearest.outputs["Index"])
original_rim = _math(group, "GREATER_THAN", original_rim, 0)
cut = _math(group, "GREATER_THAN", read_float_attribute(group, CUT_ATTRIBUTE), 0)
protected = _math(group, "MULTIPLY", _math(group, "GREATER_THAN", rim, 0),
_math(group, "MAXIMUM", cut, _math(group, "SUBTRACT", 1, original_rim)))
# Resolve planar correspondence before replacing positions with camera depth.
return sample_field(group, geometry, boundary_influence(group, protected), "FLOAT")
def smooth_outline_camera(group, original, geometry, camera, uniform_scale):
"""Update outline weights and blur ray distance in fixed-weight batches."""
nodes, links = group.nodes, group.links
controls = group_input(nodes, {"Outline Depth Fix", "Boundary Smooth"})
influence = _outline_influence(group, original, geometry)
depth = ray_distance(group, camera)
endpoints = nodes.new("GeometryNodeInputMeshEdgeVertices")
length = nodes.new("ShaderNodeVectorMath")
length.operation = "DISTANCE"
links.new(endpoints.outputs["Position 1"], length.inputs[0])
links.new(endpoints.outputs["Position 2"], length.inputs[1])
statistic = nodes.new("GeometryNodeAttributeStatistic")
statistic.data_type, statistic.domain = "FLOAT", "EDGE"
links.new(geometry, statistic.inputs["Geometry"])
links.new(length.outputs["Value"], statistic.inputs["Attribute"])
scale = _math(group, "DIVIDE", uniform_scale,
_math(group, "MAXIMUM", statistic.outputs["Median"], 1e-8))
seed = nodes.new("ShaderNodeCombineXYZ")
links.new(depth, seed.inputs["Z"])
carrier = nodes.new("GeometryNodeSetPosition")
links.new(geometry, carrier.inputs["Geometry"])
links.new(seed.outputs[0], carrier.inputs["Position"])
repeat_output = nodes.new("GeometryNodeRepeatOutput")
repeat_input = nodes.new("GeometryNodeRepeatInput")
repeat_input.pair_with_output(repeat_output)
links.new(carrier.outputs[0], repeat_input.inputs["Geometry"])
links.new(controls.outputs["Outline Depth Fix"], repeat_input.inputs["Iterations"])
position = nodes.new("GeometryNodeInputPosition")
current = nodes.new("ShaderNodeSeparateXYZ")
links.new(position.outputs[0], current.inputs[0])
reference = nodes.new("GeometryNodeBlurAttribute")
reference.data_type = "FLOAT"
reference.inputs["Iterations"].default_value = 1
reference.inputs["Weight"].default_value = 0.5
links.new(current.outputs["Z"], reference.inputs["Value"])
residual = _math(group, "ABSOLUTE", _math(group, "SUBTRACT", current.outputs["Z"], reference.outputs[0]))
ratio = _math(group, "MULTIPLY", residual, scale)
weight = nodes.new("ShaderNodeMapRange")
weight.interpolation_type, weight.clamp = "SMOOTHSTEP", True
weight.inputs["From Min"].default_value = 0.25
weight.inputs["From Max"].default_value = 0.5
links.new(ratio, weight.inputs["Value"])
active = _math(group, "MULTIPLY", influence, _math(group, "GREATER_THAN", depth, 1e-8))
active = sample_field(group, geometry, active, "FLOAT")
weighted = _math(group, "MULTIPLY", weight.outputs[0], active)
# Sampling the round's input prevents the inner Blur from updating its weight.
frozen = sample_field(group, repeat_input.outputs["Geometry"], weighted, "FLOAT")
blur = nodes.new("GeometryNodeBlurAttribute")
blur.data_type = "FLOAT"
links.new(current.outputs["Z"], blur.inputs["Value"])
links.new(frozen, blur.inputs["Weight"])
links.new(controls.outputs["Boundary Smooth"], blur.inputs["Iterations"])
updated = nodes.new("ShaderNodeCombineXYZ")
links.new(blur.outputs[0], updated.inputs["Z"])
move = nodes.new("GeometryNodeSetPosition")
links.new(repeat_input.outputs["Geometry"], move.inputs["Geometry"])
links.new(updated.outputs[0], move.inputs["Position"])
links.new(move.outputs[0], repeat_output.inputs["Geometry"])
result = sample_field(group, repeat_output.outputs["Geometry"], current.outputs["Z"], "FLOAT")
enabled = _math(group, "MULTIPLY", active,
_math(group, "MULTIPLY", controls.outputs["Outline Depth Fix"],
controls.outputs["Boundary Smooth"]))
choose = nodes.new("GeometryNodeSwitch")
choose.input_type = "FLOAT"
links.new(enabled, choose.inputs["Switch"])
links.new(depth, choose.inputs["False"])
links.new(result, choose.inputs["True"])
return reconstruct_camera_depth(group, camera, depth, choose.outputs[0])