Add tests and implement boundary smoothing and cutout functionality

- Introduced tests for boundary smoothing in `test_boundary_smoothing.py`, verifying that smoothing reduces stair-step artifacts while preserving boundary integrity.
- Added tests for cutout boundary behavior in `test_cutout_boundary.py`, ensuring proper handling of boundary triangles and normal blending.
- Created `test_temporary_attributes.py` to check the cleanup of temporary attributes across depth modifier branches, ensuring no warnings are raised.
- Implemented point culling functionality in `test_validity.py`, confirming that invalid points are removed without affecting adjacent geometry.
- Developed boundary smoothing logic in `boundary_smoothing.py`, allowing for depth smoothing while maintaining edge integrity.
- Added cutout boundary management in `cutout_boundary.py`, including functions for removing boundary triangles and tapering thickness profiles.
- Implemented validity checks in `validity.py` to ensure proper deletion of invalid vertices and associated wire edges.
This commit is contained in:
2026-09-11 20:37:20 +08:00
parent e8129fe495
commit 31074bd511
85 changed files with 2501 additions and 1451 deletions

View File

@@ -0,0 +1,45 @@
"""Clean cutout boundary triangles and taper the split thickness profile."""
from .depth_surface import _math
from .nodes import evaluate_field, group_input, read_float_attribute, sample_field, store_float_attribute
from .smoothing import edge_boundary_field
from .boundary_smoothing import boundary_influence
def remove_boundary_triangles(group, geometry):
"""Remove all-boundary triangles once on the current topology."""
nodes, links = group.nodes, group.links
rim = evaluate_field(group, edge_boundary_field(nodes, links), "FLOAT", "POINT")
all_boundary = _math(group, "GREATER_THAN", evaluate_field(group, rim, "FLOAT", "FACE"), 0.99999)
count = nodes.new("GeometryNodeInputMeshFaceNeighbors").outputs["Vertex Count"]
selected = _math(group, "MULTIPLY", all_boundary, _math(group, "COMPARE", count, 3))
delete = nodes.new("GeometryNodeDeleteGeometry")
delete.domain, delete.mode = "FACE", "ALL"
links.new(geometry, delete.inputs["Geometry"])
links.new(selected, delete.inputs["Selection"])
return delete.outputs["Geometry"]
def taper_split_profile(group, original, geometry):
"""Taper balloon thickness through the shared two-ring influence builder."""
nodes, links = group.nodes, group.links
uv = nodes.new("GeometryNodeInputNamedAttribute")
uv.data_type = "FLOAT_VECTOR"
uv.inputs["Name"].default_value = "UVMap"
original_uv = nodes.new("GeometryNodeSetPosition")
links.new(original, original_uv.inputs["Geometry"])
links.new(uv.outputs["Attribute"], original_uv.inputs["Position"])
nearest = nodes.new("GeometryNodeSampleNearest")
nearest.domain = "POINT"
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)
controls = group_input(nodes, {"Edge Round", "Split Threshold"})
strength = _math(group, "ADD", 1, _math(group, "MULTIPLY", controls.outputs["Edge Round"], 4))
band = _math(group, "MINIMUM", _math(group, "MULTIPLY", influence, strength), 1)
band = _math(group, "MULTIPLY", band, _math(group, "GREATER_THAN", controls.outputs["Split Threshold"], 0))
retained = _math(group, "SQRT", _math(group, "MAXIMUM", _math(group, "SUBTRACT", 1, band), 0))
profile = _math(group, "MULTIPLY", read_float_attribute(group, "o_balloon"), retained)
return store_float_attribute(group, geometry, "o_balloon", profile)