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

@@ -205,7 +205,7 @@ def test_curved_shell_smooths_normals_without_shrinking_thickness(surface, norma
def test_surface_interface_and_node_structure(surface):
obj, _, _, inputs, _ = surface
expected = ["Geometry", "Subdivide", "Thickness", "Depth Scale", "Split Threshold", "Smooth", "Valid Only",
"Reference Depth", "Normal Smooth", "Smooth Weight", "Validity Threshold", "Uniform Scale", "Depth Image"]
"Boundary Smooth", "Reference Depth", "Normal Smooth", "Smooth Weight", "Validity Threshold", "Uniform Scale", "Depth Image"]
assert list(inputs) == expected
assert inputs["Split Threshold"].default_value == 0
assert inputs["Split Threshold"].min_value == 0
@@ -307,7 +307,7 @@ def test_relief_interface_and_node_structure(relief):
@pytest.mark.parametrize("threshold", [0.0, 0.4, 0.9, 1.0])
@pytest.mark.parametrize("pattern", ["valid", "invalid", "partial", "half", "stripe"])
def test_valid_only_samples_face_centers(surface, pattern, threshold):
def test_valid_only_deletes_invalid_points_and_wire_edges(surface, pattern, threshold):
obj, set_value, _, inputs, _ = surface
set_value("Validity Threshold", threshold)
depth_image = obj.modifiers[0][inputs["Depth Image"].identifier]
@@ -329,9 +329,9 @@ def test_valid_only_samples_face_centers(surface, pattern, threshold):
set_value("Subdivide", subdivisions)
set_value("Valid Only", False)
base = evaluated(obj)
u = np.array([(base[0][list(face), 0].mean() + 2) / 4 for face in base[1]])
u = (base[0][:, 0] + 2) / 4
sampled = np.interp(u * 16 - 0.5, np.arange(16), alpha)
expected = [face for face, alpha_value in zip(base[1], sampled) if alpha_value >= threshold]
expected = [face for face in base[1] if np.all(sampled[list(face)] >= threshold)]
set_value("Valid Only", True)
culled = evaluated(obj)
assert len(culled[1]) == len(expected)
@@ -363,7 +363,7 @@ def test_validity_graph_matches_reference_domains_and_connections(surface):
obj, _, _, _, _ = surface
group = obj.modifiers[0].node_group
delete = next(node for node in group.nodes if node.bl_idname == "GeometryNodeDeleteGeometry")
assert (delete.domain, delete.mode) == ("FACE", "ALL")
assert (delete.domain, delete.mode) == ("POINT", "ALL")
compare = origin_socket(delete.inputs["Selection"].links[0].from_socket).node
assert (compare.bl_idname, compare.data_type, compare.operation) == ("FunctionNodeCompare", "FLOAT", "LESS_THAN")
assert origin_socket(compare.inputs["B"].links[0].from_socket).name == "Validity Threshold"
@@ -374,7 +374,9 @@ def test_validity_graph_matches_reference_domains_and_connections(surface):
consumers = [link for link in group.links if link.to_node.bl_idname != "NodeReroute"
and origin_socket(link.from_socket) == color.outputs["Color"]]
assert any(link.to_node == alpha.node for link in consumers)
assert origin_socket(delete.inputs["Geometry"].links[0].from_socket).node.node_tree.name == "O Image Plane"
protected = origin_socket(delete.inputs["Geometry"].links[0].from_socket).node
assert protected.bl_idname == "GeometryNodeGroup"
assert protected.node_tree.name == "O Image Plane"
@pytest.mark.parametrize("thickness", [0.0, 0.2])
@@ -412,6 +414,7 @@ def test_plane_interface_panels(source, kind):
assert [item.name for item in panels] == ["Options", "Data"]
controls = ["Reference Depth"]
if kind == "DEPTH":
controls.insert(0, "Boundary Smooth")
controls += ["Normal Smooth", "Smooth Weight", "Validity Threshold"]
else:
controls += ["Smooth Weight", "Depth Direction"]