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.
This commit is contained in:
2026-09-08 17:18:03 +08:00
parent 7dec3b7ca4
commit 196b964efc
71 changed files with 3440 additions and 1988 deletions

View File

@@ -8,7 +8,6 @@ TEMPORARY_ATTRIBUTES = {
"camera": ("FLOAT_VECTOR", "POINT"),
"surface_position": ("FLOAT_VECTOR", "POINT"),
"source_vertex": ("INT", "POINT"),
"wall_layers": ("FLOAT", "POINT"),
}
@@ -50,7 +49,7 @@ def _math(group, operation, a, b=None):
return node.outputs[0]
def split_depth_surface(group, geometry, image, strength, uniform, depth_scale):
def split_depth_surface(group, geometry, image, strength, uniform):
"""Keep each face's camera sample on its own corners before splitting."""
nodes, links = group.nodes, group.links
uv = nodes.new("GeometryNodeInputNamedAttribute")
@@ -93,7 +92,6 @@ def split_depth_surface(group, geometry, image, strength, uniform, depth_scale):
links.new(center, distance.inputs[index])
delta = _math(group, "ABSOLUTE", _math(group, "SUBTRACT", *depths))
scaled = _math(group, "MULTIPLY", delta, uniform)
scaled = _math(group, "MULTIPLY", scaled, _math(group, "ABSOLUTE", depth_scale))
# Cross multiplication avoids division and stays invariant under uniform scaling.
selected = _math(
group,
@@ -171,7 +169,7 @@ def store_surface_camera(group, geometry, original, strength):
def thicken_depth_surface(group, geometry, thickness):
"""Connect caps and wall rings by source vertex and layer identity."""
"""Connect caps with single quads by source vertex and cap identity."""
nodes, links = group.nodes, group.links
position = nodes.new("GeometryNodeInputPosition")
geometry = _store(group, geometry, "surface_position", position.outputs[0])
@@ -186,33 +184,7 @@ def thicken_depth_surface(group, geometry, thickness):
geometry = _store(
group, sheet.outputs["Geometry"], "source_vertex", index.outputs[0]
)
edge = nodes.new("GeometryNodeInputMeshEdgeVertices")
length = nodes.new("ShaderNodeVectorMath")
length.operation = "DISTANCE"
links.new(edge.outputs["Position 1"], length.inputs[0])
links.new(edge.outputs["Position 2"], length.inputs[1])
neighbors = nodes.new("GeometryNodeInputMeshEdgeNeighbors")
statistics = nodes.new("GeometryNodeAttributeStatistic")
statistics.domain = "EDGE"
statistics.data_type = "FLOAT"
links.new(geometry, statistics.inputs["Geometry"])
links.new(
_math(group, "COMPARE", neighbors.outputs["Face Count"], 1.0),
statistics.inputs["Selection"],
)
links.new(length.outputs["Value"], statistics.inputs["Attribute"])
count = _math(
group,
"CEIL",
_math(
group,
"DIVIDE",
thickness,
_math(group, "MAXIMUM", statistics.outputs["Median"], 1e-6),
),
)
count = _math(group, "MINIMUM", _math(group, "MAXIMUM", count, 1.0), 128.0)
saved = _store(group, geometry, "wall_layers", count)
saved = geometry
# Weld in an integer identity grid, never in scene coordinates. Nearby cuts
# retain distinct IDs; copies of one rim vertex share the same ID and layer.
@@ -224,41 +196,26 @@ def thicken_depth_surface(group, geometry, thickness):
links.new(_read(group, "source_vertex"), part.inputs[0])
links.new(part.outputs[0], identity.inputs[axis])
repeat_output = nodes.new("GeometryNodeRepeatOutput")
repeat_input = nodes.new("GeometryNodeRepeatInput")
repeat_input.pair_with_output(repeat_output)
repeat_output.repeat_items.clear()
repeat_output.repeat_items.new("GEOMETRY", "Geometry")
repeat_output.repeat_items.new("GEOMETRY", "Sides")
links.new(count, repeat_input.inputs["Iterations"])
links.new(saved, repeat_input.inputs["Geometry"])
extrude = nodes.new("GeometryNodeExtrudeMesh")
extrude.mode = "FACES"
extrude.inputs["Individual"].default_value = False
extrusion_offset = nodes.new("ShaderNodeCombineXYZ")
extrusion_offset.inputs["Z"].default_value = -1.0
links.new(extrusion_offset.outputs[0], extrude.inputs["Offset"])
links.new(repeat_input.outputs["Geometry"], extrude.inputs["Mesh"])
links.new(saved, extrude.inputs["Mesh"])
sides = nodes.new("GeometryNodeSeparateGeometry")
sides.domain = "FACE"
links.new(extrude.outputs["Mesh"], sides.inputs["Geometry"])
links.new(extrude.outputs["Side"], sides.inputs["Selection"])
join_sides = nodes.new("GeometryNodeJoinGeometry")
links.new(repeat_input.outputs["Sides"], join_sides.inputs["Geometry"])
links.new(sides.outputs["Selection"], join_sides.inputs["Geometry"])
links.new(sides.outputs["Inverted"], repeat_output.inputs["Geometry"])
links.new(join_sides.outputs["Geometry"], repeat_output.inputs["Sides"])
offset = nodes.new("ShaderNodeCombineXYZ")
links.new(
_math(group, "MULTIPLY", _read(group, "wall_layers"), -1.0), offset.inputs["Z"]
)
offset.inputs["Z"].default_value = -1.0
back = nodes.new("GeometryNodeSetPosition")
links.new(saved, back.inputs["Geometry"])
links.new(offset.outputs[0], back.inputs["Offset"])
flip_back = nodes.new("GeometryNodeFlipFaces")
links.new(back.outputs["Geometry"], flip_back.inputs["Mesh"])
flip_sides = nodes.new("GeometryNodeFlipFaces")
links.new(repeat_output.outputs["Sides"], flip_sides.inputs["Mesh"])
links.new(sides.outputs["Selection"], flip_sides.inputs["Mesh"])
join = nodes.new("GeometryNodeJoinGeometry")
for value in (
saved,
@@ -274,12 +231,11 @@ def thicken_depth_surface(group, geometry, thickness):
merge.inputs["Distance"].default_value = 0.1
links.new(encoded.outputs["Geometry"], merge.inputs["Geometry"])
displacement = nodes.new("ShaderNodeCombineXYZ")
step = _math(group, "DIVIDE", thickness, _read(group, "wall_layers"))
layer_position = nodes.new("GeometryNodeInputPosition")
layer_axes = nodes.new("ShaderNodeSeparateXYZ")
links.new(layer_position.outputs[0], layer_axes.inputs[0])
links.new(
_math(group, "MULTIPLY", layer_axes.outputs["Z"], step),
_math(group, "MULTIPLY", layer_axes.outputs["Z"], thickness),
displacement.inputs["Z"],
)
restored = nodes.new("ShaderNodeVectorMath")