feat: add tests for outline points and thickness handling in depth surface

This commit is contained in:
2026-09-16 00:29:30 +08:00
parent 2dfa9b2add
commit 474fc10f0f
4 changed files with 45 additions and 6 deletions

Binary file not shown.

View File

@@ -145,6 +145,34 @@ def test_planar_depth_preserves_blocks_and_cleans_single_face_width(resolution):
np.testing.assert_allclose(coords, original, atol=1e-6) np.testing.assert_allclose(coords, original, atol=1e-6)
def test_outline_points_use_adjacent_face_depth_without_split():
obj, set_value = surface(step=0.0, resolution=2)
image = bpy.data.images["Camera"]
pixels = np.array(image.pixels[:], np.float32).reshape(32, 64, 4)
pixels[..., 2] = 1
# Spike both edge rows to cover Blender's image origin without relying on it.
pixels[(0, -1), :, 2] = 10
image.pixels.foreach_set(pixels.ravel())
image.update()
obj.update_tag(refresh={"DATA"})
bpy.context.view_layer.update()
set_value("Depth Split", 0.0)
coords, faces = evaluated(obj)
boundary = np.isclose(coords[:, 2], 0, atol=0.05) | np.isclose(coords[:, 2], 1, atol=0.05)
assert boundary.any()
np.testing.assert_allclose(coords[boundary, 1], 0, atol=1e-5)
def test_thickness_handles_more_than_4096_source_vertices():
obj, set_value = surface(step=0.0, resolution=45)
assert len(obj.data.vertices) > 4096
set_value("Depth Split", 0.0)
set_value("Thickness", 0.02)
coords, faces = evaluated(obj)
assert np.isfinite(coords).all()
assert_closed(faces)
def test_small_positive_split_preserves_unsplit_projection(): def test_small_positive_split_preserves_unsplit_projection():
obj, set_value = surface(step=2.0) obj, set_value = surface(step=2.0)
original, original_faces = evaluated(obj) original, original_faces = evaluated(obj)

View File

@@ -240,13 +240,21 @@ def split_depth_surface(group, geometry, face_camera, strength, reference_depth,
return choose.outputs["Output"], face_camera, cut return choose.outputs["Output"], face_camera, cut
def build_surface_camera(group, original, separated, cut_vertex, strength): def build_surface_camera(group, original, separated, cut_vertex, strength, *, always_vertex=None):
"""Change cut depth along the original ray, preserving every other vertex.""" """Change cut depth along the original ray, preserving every other vertex."""
enabled = group.nodes.new("GeometryNodeSwitch") enabled = group.nodes.new("GeometryNodeSwitch")
enabled.input_type = "BOOLEAN" enabled.input_type = "BOOLEAN"
group.links.new(_math(group, "GREATER_THAN", strength, 0.0), enabled.inputs["Switch"]) group.links.new(_math(group, "GREATER_THAN", strength, 0.0), enabled.inputs["Switch"])
enabled.inputs["False"].default_value = False enabled.inputs["False"].default_value = False
group.links.new(cut_vertex, enabled.inputs["True"]) group.links.new(cut_vertex, enabled.inputs["True"])
if always_vertex is not None:
combined = group.nodes.new("FunctionNodeBooleanMath")
combined.operation = "OR"
group.links.new(enabled.outputs["Output"], combined.inputs[0])
group.links.new(always_vertex, combined.inputs[1])
enabled = combined.outputs["Boolean"]
else:
enabled = enabled.outputs["Output"]
original_axes = group.nodes.new("ShaderNodeSeparateXYZ") original_axes = group.nodes.new("ShaderNodeSeparateXYZ")
separated_axes = group.nodes.new("ShaderNodeSeparateXYZ") separated_axes = group.nodes.new("ShaderNodeSeparateXYZ")
group.links.new(original, original_axes.inputs[0]) group.links.new(original, original_axes.inputs[0])
@@ -260,7 +268,7 @@ def build_surface_camera(group, original, separated, cut_vertex, strength):
group.links.new(ratio, projected.inputs[3]) group.links.new(ratio, projected.inputs[3])
choose = group.nodes.new("GeometryNodeSwitch") choose = group.nodes.new("GeometryNodeSwitch")
choose.input_type = "VECTOR" choose.input_type = "VECTOR"
group.links.new(enabled.outputs["Output"], choose.inputs["Switch"]) group.links.new(enabled, choose.inputs["Switch"])
group.links.new(original, choose.inputs["False"]) group.links.new(original, choose.inputs["False"])
group.links.new(projected.outputs[0], choose.inputs["True"]) group.links.new(projected.outputs[0], choose.inputs["True"])
return evaluate_field(group, choose.outputs["Output"], "FLOAT_VECTOR", "POINT") return evaluate_field(group, choose.outputs["Output"], "FLOAT_VECTOR", "POINT")
@@ -275,7 +283,7 @@ def thicken_depth_surface(group, geometry, thickness, normal_smooth=None, *,
# Encode identity in the construction geometry before extrusion. The source # Encode identity in the construction geometry before extrusion. The source
# surface remains a separate sampling input; no position or ID is stored. # surface remains a separate sampling input; no position or ID is stored.
identity = nodes.new("ShaderNodeCombineXYZ") identity = nodes.new("ShaderNodeCombineXYZ")
for operation, axis in (("MODULO", "X"), ("DIVIDE", "Y")): for operation, axis in (("MODULO", "X"), ("DIVIDE", "Z")):
part = nodes.new("FunctionNodeIntegerMath") part = nodes.new("FunctionNodeIntegerMath")
part.operation = operation part.operation = operation
part.inputs[1].default_value = 4096 part.inputs[1].default_value = 4096

View File

@@ -269,7 +269,10 @@ def _build_depth_surface(group, geometry, controls):
group, geometry, sample_face_camera(group, image), split, controls.outputs["Reference Depth"], triangles=True, group, geometry, sample_face_camera(group, image), split, controls.outputs["Reference Depth"], triangles=True,
) )
geometry = remove_boundary_triangles(group, geometry) geometry = remove_boundary_triangles(group, geometry)
camera = build_surface_camera(group, _position_field(group, image), corner_camera, cut_vertex, split) outline = evaluate_field(group, edge_boundary_field(group.nodes, group.links), "BOOLEAN", "POINT")
camera = build_surface_camera(
group, _position_field(group, image), corner_camera, cut_vertex, split, always_vertex=outline,
)
projection = project_depth_surface( projection = project_depth_surface(
group, geometry, camera, scale, controls.outputs["Reference Depth"], controls.outputs["Depth Scale"], split, group, geometry, camera, scale, controls.outputs["Reference Depth"], controls.outputs["Depth Scale"], split,
) )