Files
AnyImage/tools/node_assets/cutout_shapes.py
nan 196b964efc Add cutout shell and volume node assets for depth volume processing
- Implemented `cutout_shell.py` to build front normals and shell positions for balloon geometry.
- Created `cutout_volume.py` to establish smooth center fields and manage balloon surface attributes.
- Introduced `pisa_sr_export.py` for exporting PiSA-SR components with dependencies and asset finalization.
2026-09-08 17:18:03 +08:00

614 lines
25 KiB
Python

"""Construct the planar and depth-based Cutout shape stages."""
from cutout_depth_surface import store_surface_camera, remove_surface_attributes, split_depth_surface, thicken_depth_surface
from anyimage.operators.cutout_tool.shape import BALLOON_ATTRIBUTE_NAME
def _socket(group, name, in_out, socket_type, parent=None):
socket = group.interface.new_socket(
name=name,
in_out=in_out,
socket_type=socket_type,
parent=parent,
)
if in_out == "INPUT" and hasattr(socket, "structure_type"):
socket.structure_type = "SINGLE"
return socket
def _group_input(nodes, location, visible_names):
node = nodes.new("NodeGroupInput")
node.location = location
visible = set(visible_names)
for output in node.outputs:
output.hide = output.name not in visible
return node
def _float_input(
group,
name,
default,
minimum=0.0,
maximum=None,
subtype=None,
parent=None,
):
socket = _socket(group, name, "INPUT", "NodeSocketFloat", parent)
socket.default_value = default
socket.min_value = minimum
if maximum is not None:
socket.max_value = maximum
if subtype is not None:
socket.subtype = subtype
return socket
def _bool_input(group, name, default=False, parent=None):
socket = _socket(group, name, "INPUT", "NodeSocketBool", parent)
socket.default_value = default
return socket
def _vector_input(group, name, default, subtype=None, parent=None):
socket = _socket(group, name, "INPUT", "NodeSocketVector", parent)
socket.default_value = default
if subtype is not None:
socket.subtype = subtype
return socket
def _math(nodes, operation, location):
node = nodes.new("ShaderNodeMath")
node.operation = operation
node.location = location
return node
def _island_cleanup(group, geometry, cleanup, location):
nodes = group.nodes
links = group.links
x, y = location
mesh_island = nodes.new("GeometryNodeInputMeshIsland")
mesh_island.location = (x, y + 250.0)
face_area = nodes.new("GeometryNodeInputMeshFaceArea")
face_area.location = (x, y + 100.0)
island_area = nodes.new("GeometryNodeAccumulateField")
island_area.data_type = "FLOAT"
island_area.domain = "FACE"
island_area.location = (x + 220.0, y + 200.0)
links.new(face_area.outputs["Area"], island_area.inputs["Value"])
links.new(mesh_island.outputs["Island Index"], island_area.inputs["Group ID"])
tiny = _math(nodes, "LESS_THAN", (x + 450.0, y + 200.0))
links.new(island_area.outputs["Total"], tiny.inputs[0])
links.new(cleanup, tiny.inputs[1])
delete = nodes.new("GeometryNodeDeleteGeometry")
delete.domain = "FACE"
delete.location = (x + 700.0, y)
links.new(geometry, delete.inputs["Geometry"])
links.new(tiny.outputs[0], delete.inputs["Selection"])
return delete.outputs["Geometry"]
def _profile_field(group, geometry, location):
nodes = group.nodes
links = group.links
x, y = location
profile = nodes.new("GeometryNodeInputNamedAttribute")
profile.data_type = "FLOAT"
profile.inputs["Name"].default_value = BALLOON_ATTRIBUTE_NAME
profile.location = (x, y + 250.0)
profile_max = nodes.new("GeometryNodeAttributeStatistic")
profile_max.data_type = "FLOAT"
profile_max.domain = "POINT"
profile_max.location = (x + 220.0, y)
links.new(geometry, profile_max.inputs["Geometry"])
links.new(profile.outputs["Attribute"], profile_max.inputs["Attribute"])
safe_max = _math(nodes, "MAXIMUM", (x + 450.0, y))
safe_max.inputs[1].default_value = 1e-6
links.new(profile_max.outputs["Max"], safe_max.inputs[0])
normalized = _math(nodes, "DIVIDE", (x + 450.0, y + 250.0))
links.new(profile.outputs["Attribute"], normalized.inputs[0])
links.new(safe_max.outputs[0], normalized.inputs[1])
smooth = nodes.new("ShaderNodeMapRange")
smooth.data_type = "FLOAT"
smooth.interpolation_type = "SMOOTHERSTEP"
smooth.clamp = True
smooth.inputs["From Min"].default_value = 0.0
smooth.inputs["From Max"].default_value = 1.0
smooth.inputs["To Min"].default_value = 0.0
smooth.inputs["To Max"].default_value = 1.0
smooth.location = (x + 650.0, y + 250.0)
links.new(normalized.outputs[0], smooth.inputs["Value"])
return profile.outputs["Attribute"], smooth.outputs["Result"]
def _extrude_layers(group, geometry, location):
node = group.nodes.new("GeometryNodeExtrudeMesh")
node.mode = "FACES"
node.inputs["Individual"].default_value = False
node.inputs["Offset"].default_value = (0.0, 0.0, 1.0)
# Keep distinct front and back vertices before overriding their Z positions.
node.inputs["Offset Scale"].default_value = 1.0
node.location = location
group.links.new(geometry, node.inputs["Mesh"])
return node
def _offset_geometry_z(group, geometry, z_value, location):
nodes = group.nodes
links = group.links
offset = nodes.new("ShaderNodeCombineXYZ")
offset.location = location
links.new(z_value, offset.inputs["Z"])
set_position = nodes.new("GeometryNodeSetPosition")
set_position.location = (location[0] + 200.0, location[1])
links.new(geometry, set_position.inputs["Geometry"])
links.new(offset.outputs["Vector"], set_position.inputs["Offset"])
return set_position.outputs["Geometry"]
def _set_layer_z(group, geometry, extrude, front_z, back_z, location):
nodes = group.nodes
links = group.links
choose_z = nodes.new("GeometryNodeSwitch")
choose_z.input_type = "FLOAT"
choose_z.location = (location[0], location[1] - 200.0)
links.new(extrude.outputs["Top"], choose_z.inputs["Switch"])
links.new(front_z, choose_z.inputs["False"])
links.new(back_z, choose_z.inputs["True"])
position = nodes.new("GeometryNodeInputPosition")
position.location = (location[0], location[1] + 150.0)
separate = nodes.new("ShaderNodeSeparateXYZ")
separate.location = (location[0] + 200.0, location[1] + 150.0)
links.new(position.outputs["Position"], separate.inputs["Vector"])
combine = nodes.new("ShaderNodeCombineXYZ")
combine.location = (location[0] + 400.0, location[1])
links.new(separate.outputs["X"], combine.inputs["X"])
links.new(separate.outputs["Y"], combine.inputs["Y"])
links.new(choose_z.outputs["Output"], combine.inputs["Z"])
set_position = nodes.new("GeometryNodeSetPosition")
set_position.location = (location[0] + 600.0, location[1])
links.new(extrude.outputs["Mesh"], set_position.inputs["Geometry"])
links.new(combine.outputs["Vector"], set_position.inputs["Position"])
side = nodes.new("GeometryNodeSeparateGeometry")
side.domain = "FACE"
side.location = (location[0] + 850.0, location[1] + 200.0)
links.new(set_position.outputs["Geometry"], side.inputs["Geometry"])
links.new(extrude.outputs["Side"], side.inputs["Selection"])
flip_side = nodes.new("GeometryNodeFlipFaces")
flip_side.location = (location[0] + 850.0, location[1] + 400.0)
links.new(side.outputs["Selection"], flip_side.inputs["Mesh"])
front = _offset_geometry_z(
group, geometry, front_z, (location[0], location[1] - 450.0)
)
back = _offset_geometry_z(
group, geometry, back_z, (location[0], location[1] - 700.0)
)
flip_back = nodes.new("GeometryNodeFlipFaces")
flip_back.location = (location[0] + 500.0, location[1] - 700.0)
links.new(back, flip_back.inputs["Mesh"])
join = nodes.new("GeometryNodeJoinGeometry")
join.location = (location[0] + 1100.0, location[1])
links.new(front, join.inputs["Geometry"])
links.new(flip_back.outputs["Mesh"], join.inputs["Geometry"])
links.new(flip_side.outputs["Mesh"], join.inputs["Geometry"])
merge = nodes.new("GeometryNodeMergeByDistance")
merge.mode = "ALL"
merge.inputs["Distance"].default_value = 1e-6
merge.location = (location[0] + 1300.0, location[1])
links.new(join.outputs["Geometry"], merge.inputs["Geometry"])
return merge.outputs["Geometry"]
def _set_segmented_layer_z(
group,
geometry,
front_z,
back_z,
thickness,
location,
):
nodes = group.nodes
links = group.links
x, y = location
edge_vertices = nodes.new("GeometryNodeInputMeshEdgeVertices")
edge_vertices.location = (x, y + 850.0)
edge_length = nodes.new("ShaderNodeVectorMath")
edge_length.operation = "DISTANCE"
edge_length.location = (x + 220.0, y + 850.0)
links.new(edge_vertices.outputs["Position 1"], edge_length.inputs[0])
links.new(edge_vertices.outputs["Position 2"], edge_length.inputs[1])
edge_neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
edge_neighbors.location = (x, y + 650.0)
boundary = _math(nodes, "COMPARE", (x + 220.0, y + 650.0))
boundary.inputs[1].default_value = 1.0
boundary.inputs[2].default_value = 0.1
links.new(edge_neighbors.outputs["Face Count"], boundary.inputs[0])
statistics = nodes.new("GeometryNodeAttributeStatistic")
statistics.data_type = "FLOAT"
statistics.domain = "EDGE"
statistics.location = (x + 450.0, y + 750.0)
links.new(geometry, statistics.inputs["Geometry"])
links.new(boundary.outputs[0], statistics.inputs["Selection"])
links.new(edge_length.outputs["Value"], statistics.inputs["Attribute"])
safe_edge = _math(nodes, "MAXIMUM", (x + 680.0, y + 750.0))
safe_edge.inputs[1].default_value = 1e-6
links.new(statistics.outputs["Median"], safe_edge.inputs[0])
segment_ratio = _math(nodes, "DIVIDE", (x + 900.0, y + 750.0))
links.new(thickness, segment_ratio.inputs[0])
links.new(safe_edge.outputs[0], segment_ratio.inputs[1])
segment_count = nodes.new("FunctionNodeFloatToInt")
segment_count.rounding_mode = "CEILING"
segment_count.location = (x + 1120.0, y + 750.0)
links.new(segment_ratio.outputs[0], segment_count.inputs["Float"])
at_least_one = nodes.new("FunctionNodeIntegerMath")
at_least_one.operation = "MAXIMUM"
at_least_one.inputs[1].default_value = 1
at_least_one.location = (x + 1340.0, y + 750.0)
links.new(segment_count.outputs["Integer"], at_least_one.inputs[0])
segment_limit = nodes.new("FunctionNodeIntegerMath")
segment_limit.operation = "MINIMUM"
segment_limit.inputs[1].default_value = 128
segment_limit.location = (x + 1560.0, y + 750.0)
links.new(at_least_one.outputs[0], segment_limit.inputs[0])
step = _math(nodes, "DIVIDE", (x + 1560.0, y + 500.0))
links.new(thickness, step.inputs[0])
links.new(segment_limit.outputs[0], step.inputs[1])
front = _offset_geometry_z(group, geometry, front_z, (x, y + 100.0))
repeat_output = nodes.new("GeometryNodeRepeatOutput")
repeat_output.location = (x + 2380.0, y + 100.0)
repeat_input = nodes.new("GeometryNodeRepeatInput")
repeat_input.pair_with_output(repeat_output)
repeat_output.repeat_items.new("GEOMETRY", "Geometry")
repeat_output.repeat_items.new("GEOMETRY", "Sides")
repeat_input.location = (x + 1560.0, y + 100.0)
links.new(segment_limit.outputs[0], repeat_input.inputs["Iterations"])
links.new(geometry, repeat_input.inputs["Geometry"])
negative_step = _math(nodes, "MULTIPLY", (x + 1760.0, y - 100.0))
negative_step.inputs[1].default_value = -1.0
links.new(step.outputs[0], negative_step.inputs[0])
extrude = nodes.new("GeometryNodeExtrudeMesh")
extrude.mode = "FACES"
extrude.inputs["Individual"].default_value = False
extrude.inputs["Selection"].default_value = True
extrude.inputs["Offset"].default_value = (0.0, 0.0, 0.0)
extrude.location = (x + 1960.0, y + 100.0)
links.new(negative_step.outputs[0], extrude.inputs["Offset Scale"])
links.new(repeat_input.outputs["Geometry"], extrude.inputs["Mesh"])
split_extrude = nodes.new("GeometryNodeSeparateGeometry")
split_extrude.domain = "FACE"
split_extrude.location = (x + 2160.0, y + 100.0)
links.new(extrude.outputs["Mesh"], split_extrude.inputs["Geometry"])
links.new(extrude.outputs["Side"], split_extrude.inputs["Selection"])
join_sides = nodes.new("GeometryNodeJoinGeometry")
join_sides.location = (x + 2160.0, y - 150.0)
links.new(repeat_input.outputs["Sides"], join_sides.inputs["Geometry"])
links.new(split_extrude.outputs["Selection"], join_sides.inputs["Geometry"])
links.new(split_extrude.outputs["Inverted"], repeat_output.inputs["Geometry"])
links.new(join_sides.outputs["Geometry"], repeat_output.inputs["Sides"])
positioned_side = _offset_geometry_z(
group,
repeat_output.outputs["Sides"],
front_z,
(x + 2450.0, y + 350.0),
)
flip_side = nodes.new("GeometryNodeFlipFaces")
flip_side.location = (x + 2850.0, y + 350.0)
links.new(positioned_side, flip_side.inputs["Mesh"])
back = _offset_geometry_z(group, geometry, back_z, (x + 2450.0, y - 400.0))
flip_back = nodes.new("GeometryNodeFlipFaces")
flip_back.location = (x + 2850.0, y - 400.0)
links.new(back, flip_back.inputs["Mesh"])
join = nodes.new("GeometryNodeJoinGeometry")
join.location = (x + 3050.0, y + 50.0)
links.new(front, join.inputs["Geometry"])
links.new(flip_back.outputs["Mesh"], join.inputs["Geometry"])
links.new(flip_side.outputs["Mesh"], join.inputs["Geometry"])
merge = nodes.new("GeometryNodeMergeByDistance")
merge.mode = "ALL"
merge.inputs["Distance"].default_value = 1e-6
merge.location = (x + 3250.0, y + 50.0)
links.new(join.outputs["Geometry"], merge.inputs["Geometry"])
return merge.outputs["Geometry"]
def _set_geometry_z(group, geometry, z_value, location):
nodes = group.nodes
links = group.links
position = nodes.new("GeometryNodeInputPosition")
position.location = location
separate = nodes.new("ShaderNodeSeparateXYZ")
separate.location = (location[0] + 200.0, location[1])
links.new(position.outputs["Position"], separate.inputs["Vector"])
combine = nodes.new("ShaderNodeCombineXYZ")
combine.location = (location[0] + 400.0, location[1])
links.new(separate.outputs["X"], combine.inputs["X"])
links.new(separate.outputs["Y"], combine.inputs["Y"])
links.new(z_value, combine.inputs["Z"])
set_position = nodes.new("GeometryNodeSetPosition")
set_position.location = (location[0] + 600.0, location[1])
links.new(geometry, set_position.inputs["Geometry"])
links.new(combine.outputs["Vector"], set_position.inputs["Position"])
return set_position.outputs["Geometry"]
def _remove_zero_thickness(
group,
zero_geometry,
shaped_geometry,
thickness,
location,
):
nodes = group.nodes
links = group.links
zero = _math(nodes, "LESS_THAN", (location[0], location[1] - 200.0))
zero.inputs[1].default_value = 1e-6
links.new(thickness, zero.inputs[0])
choose = nodes.new("GeometryNodeSwitch")
choose.input_type = "GEOMETRY"
choose.location = location
links.new(zero.outputs[0], choose.inputs["Switch"])
links.new(shaped_geometry, choose.inputs["False"])
links.new(zero_geometry, choose.inputs["True"])
return choose.outputs["Output"]
def _position_field(group, depth_image, location):
nodes = group.nodes
links = group.links
uv = nodes.new("GeometryNodeInputNamedAttribute")
uv.data_type = "FLOAT_VECTOR"
uv.inputs["Name"].default_value = "UVMap"
uv.location = location
position_texture = nodes.new("GeometryNodeImageTexture")
position_texture.interpolation = "Linear"
position_texture.extension = "EXTEND"
position_texture.location = (location[0] + 220.0, location[1])
links.new(depth_image, position_texture.inputs["Image"])
links.new(uv.outputs["Attribute"], position_texture.inputs["Vector"])
return position_texture.outputs["Color"]
def _plane_depth(group, direction, reference_depth, location):
nodes = group.nodes
links = group.links
normal = nodes.new("ShaderNodeVectorMath")
normal.operation = "NORMALIZE"
normal.location = location
links.new(direction, normal.inputs[0])
normal_components = nodes.new("ShaderNodeSeparateXYZ")
normal_components.location = (location[0] + 220.0, location[1])
links.new(normal.outputs["Vector"], normal_components.inputs["Vector"])
position = nodes.new("GeometryNodeInputPosition")
position.location = (location[0], location[1] + 450.0)
position_components = nodes.new("ShaderNodeSeparateXYZ")
position_components.location = (location[0] + 220.0, location[1] + 450.0)
links.new(position.outputs["Position"], position_components.inputs["Vector"])
x_term = _math(nodes, "MULTIPLY", (location[0] + 440.0, location[1] + 850.0))
links.new(position_components.outputs["X"], x_term.inputs[0])
links.new(normal_components.outputs["X"], x_term.inputs[1])
y_term = _math(nodes, "MULTIPLY", (location[0] + 440.0, location[1] + 350.0))
links.new(position_components.outputs["Y"], y_term.inputs[0])
links.new(normal_components.outputs["Y"], y_term.inputs[1])
point_dot = _math(nodes, "ADD", (location[0] + 660.0, location[1] + 400.0))
links.new(x_term.outputs[0], point_dot.inputs[0])
links.new(y_term.outputs[0], point_dot.inputs[1])
divide = _math(nodes, "DIVIDE", (location[0] + 880.0, location[1] + 200.0))
links.new(point_dot.outputs[0], divide.inputs[0])
links.new(normal_components.outputs["Z"], divide.inputs[1])
plane = _math(nodes, "ADD", (location[0] + 1100.0, location[1] + 200.0))
links.new(reference_depth, plane.inputs[0])
links.new(divide.outputs[0], plane.inputs[1])
return plane.outputs[0]
def _shade_output(group, geometry, location):
shade = group.nodes.new("GeometryNodeSetShadeSmooth")
shade.domain = "FACE"
shade.inputs["Shade Smooth"].default_value = True
shade.location = location
output = group.nodes.new("NodeGroupOutput")
output.location = (location[0] + 250.0, location[1])
group.links.new(geometry, shade.inputs["Geometry"])
group.links.new(shade.outputs["Geometry"], output.inputs["Geometry"])
def _build_cutout(group, geometry, thickness):
nodes = group.nodes
links = group.links
half = _math(nodes, "MULTIPLY", (-200.0, 50.0))
half.inputs[1].default_value = 0.5
links.new(thickness, half.inputs[0])
back = _math(nodes, "MULTIPLY", (20.0, 0.0))
back.inputs[1].default_value = -1.0
links.new(half.outputs[0], back.inputs[0])
shaped = _set_segmented_layer_z(
group,
geometry,
half.outputs[0],
back.outputs[0],
thickness,
(250.0, 300.0),
)
return _remove_zero_thickness(group, geometry, shaped, thickness, (3750.0, 300.0))
def _build_balloon(group, geometry, thickness):
nodes = group.nodes
links = group.links
profile, _profile_factor = _profile_field(group, geometry, (-200.0, 0.0))
front_scale = _math(nodes, "MULTIPLY", (2600.0, 500.0))
links.new(profile, front_scale.inputs[0])
links.new(thickness, front_scale.inputs[1])
back = _math(nodes, "MULTIPLY", (2600.0, 300.0))
links.new(profile, back.inputs[0])
links.new(thickness, back.inputs[1])
negative_back = _math(nodes, "MULTIPLY", (2800.0, 300.0))
negative_back.inputs[1].default_value = -1.0
links.new(back.outputs[0], negative_back.inputs[0])
extrude = _extrude_layers(group, geometry, (2400.0, 750.0))
shaped = _set_layer_z(
group,
geometry,
extrude,
front_scale.outputs[0],
negative_back.outputs[0],
(2850.0, 650.0),
)
return _remove_zero_thickness(group, geometry, shaped, thickness, (4400.0, 650.0))
def _build_depth_balloon(
group,
geometry,
thickness,
depth_scale,
double_sided,
uniform_scale,
depth_direction,
depth_image,
depth_reference,
):
nodes = group.nodes
links = group.links
profile, profile_factor = _profile_field(group, geometry, (-200.0, 250.0))
camera_position = _position_field(group, depth_image, (200.0, -250.0))
separate_camera = nodes.new("ShaderNodeSeparateXYZ")
separate_camera.location = (650.0, -250.0)
links.new(camera_position, separate_camera.inputs["Vector"])
metric_depth = _math(nodes, "MULTIPLY", (900.0, -250.0))
links.new(separate_camera.outputs["Z"], metric_depth.inputs[0])
links.new(uniform_scale, metric_depth.inputs[1])
plane_depth = _plane_depth(
group,
depth_direction,
depth_reference,
(600.0, -850.0),
)
corrected_depth = _math(nodes, "SUBTRACT", (1750.0, -250.0))
links.new(plane_depth, corrected_depth.inputs[0])
links.new(metric_depth.outputs[0], corrected_depth.inputs[1])
nonnegative_depth = _math(nodes, "MAXIMUM", (1950.0, -250.0))
nonnegative_depth.inputs[1].default_value = 0.0
links.new(corrected_depth.outputs[0], nonnegative_depth.inputs[0])
scaled_depth = _math(nodes, "MULTIPLY", (2150.0, -250.0))
links.new(nonnegative_depth.outputs[0], scaled_depth.inputs[0])
links.new(depth_scale, scaled_depth.inputs[1])
front_depth = scaled_depth.outputs[0]
base_front = _math(nodes, "MULTIPLY", (2400.0, 500.0))
links.new(profile, base_front.inputs[0])
links.new(thickness, base_front.inputs[1])
base_back = _math(nodes, "MULTIPLY", (2600.0, 300.0))
links.new(profile, base_back.inputs[0])
links.new(thickness, base_back.inputs[1])
negate_back = _math(nodes, "MULTIPLY", (2800.0, 300.0))
negate_back.inputs[1].default_value = -1.0
links.new(base_back.outputs[0], negate_back.inputs[0])
front_mix = nodes.new("ShaderNodeMix")
front_mix.data_type = "FLOAT"
front_mix.clamp_factor = True
front_mix.location = (2600.0, 50.0)
links.new(profile_factor, front_mix.inputs["Factor"])
links.new(base_front.outputs[0], front_mix.inputs["A"])
links.new(front_depth, front_mix.inputs["B"])
mirrored_front = _math(nodes, "MULTIPLY", (2850.0, -150.0))
mirrored_front.inputs[1].default_value = -1.0
links.new(front_mix.outputs["Result"], mirrored_front.inputs[0])
back_choice = nodes.new("GeometryNodeSwitch")
back_choice.input_type = "FLOAT"
back_choice.location = (3050.0, 100.0)
links.new(double_sided, back_choice.inputs["Switch"])
links.new(negate_back.outputs[0], back_choice.inputs["False"])
links.new(mirrored_front.outputs[0], back_choice.inputs["True"])
extrude = _extrude_layers(group, geometry, (2400.0, 750.0))
shaped = _set_layer_z(
group,
geometry,
extrude,
front_mix.outputs["Result"],
back_choice.outputs["Output"],
(3300.0, 650.0),
)
front_only = _set_geometry_z(
group, geometry, front_mix.outputs["Result"], (3500.0, 100.0)
)
return _remove_zero_thickness(group, front_only, shaped, thickness, (4850.0, 600.0))
def _build_depth_surface(
group,
geometry,
thickness,
depth_scale,
split,
uniform_scale,
reference_depth,
depth_image,
):
nodes = group.nodes
links = group.links
geometry = split_depth_surface(
group, geometry, depth_image, split, uniform_scale
)
original_camera = _position_field(group, depth_image, (-200.0, -200.0))
geometry, camera_position = store_surface_camera(
group, geometry, original_camera, split
)
scaled = nodes.new("ShaderNodeVectorMath")
scaled.operation = "SCALE"
scaled.location = (3100.0, -100.0)
links.new(camera_position, scaled.inputs[0])
links.new(uniform_scale, scaled.inputs[3])
separate = nodes.new("ShaderNodeSeparateXYZ")
separate.location = (3300.0, -100.0)
links.new(scaled.outputs["Vector"], separate.inputs["Vector"])
relative_depth = _math(nodes, "SUBTRACT", (3500.0, -100.0))
links.new(reference_depth, relative_depth.inputs[0])
links.new(separate.outputs["Z"], relative_depth.inputs[1])
front = _math(nodes, "MULTIPLY", (3700.0, -100.0))
links.new(relative_depth.outputs[0], front.inputs[0])
links.new(depth_scale, front.inputs[1])
negative_y = _math(nodes, "MULTIPLY", (3500.0, -600.0))
negative_y.inputs[1].default_value = -1.0
links.new(separate.outputs["Y"], negative_y.inputs[0])
projected = nodes.new("ShaderNodeCombineXYZ")
projected.location = (3700.0, -450.0)
links.new(separate.outputs["X"], projected.inputs["X"])
links.new(negative_y.outputs[0], projected.inputs["Y"])
current_position = nodes.new("GeometryNodeInputPosition")
current_position.location = (3500.0, -850.0)
blend = nodes.new("ShaderNodeMix")
blend.data_type = "VECTOR"
blend.clamp_factor = False
blend.location = (3900.0, -450.0)
links.new(depth_scale, blend.inputs[0])
links.new(current_position.outputs["Position"], blend.inputs[4])
links.new(projected.outputs["Vector"], blend.inputs[5])
blended_axes = nodes.new("ShaderNodeSeparateXYZ")
blended_axes.location = (4100.0, -500.0)
links.new(blend.outputs["Result"], blended_axes.inputs["Vector"])
surface_position = nodes.new("ShaderNodeCombineXYZ")
surface_position.location = (4100.0, -200.0)
links.new(blended_axes.outputs["X"], surface_position.inputs["X"])
links.new(blended_axes.outputs["Y"], surface_position.inputs["Y"])
links.new(front.outputs[0], surface_position.inputs["Z"])
project_geometry = nodes.new("GeometryNodeSetPosition")
project_geometry.location = (4400.0, 400.0)
links.new(geometry, project_geometry.inputs["Geometry"])
links.new(surface_position.outputs["Vector"], project_geometry.inputs["Position"])
geometry = project_geometry.outputs["Geometry"]
shaped = thicken_depth_surface(group, geometry, thickness)
geometry = _remove_zero_thickness(
group, project_geometry.outputs["Geometry"], shaped, thickness, (11800.0, 400.0)
)
return remove_surface_attributes(group, geometry)