Refactor depth surface handling and unify depth sampling rules

- Removed the `always_vertex` parameter from `build_surface_camera` to streamline depth sampling for cutouts and ensure consistent behavior across depth plane and panorama.
- Updated depth sampling logic to ensure that all free boundary points, including the original frame of the depth plane, use neighboring face depth instead of their own texture sampling.
- Adjusted the depth mask deletion process to operate on faces rather than points, eliminating the need for additional cleanup of dangling edges.
- Ensured that the cutout rules are consistently applied across all relevant nodes, removing exceptions and simplifying the logic.
- Updated tests to verify the new behavior, ensuring that cutout points follow the depth of adjacent faces and that the original frame maintains its depth sampling.
- Refactored geometry handling in multiple depth-related nodes to improve clarity and maintainability.
This commit is contained in:
2026-09-16 16:30:56 +08:00
parent 7f69630371
commit c62ec2cd48
28 changed files with 880 additions and 173 deletions

View File

@@ -113,6 +113,7 @@ def test_symmetry_interface_and_cutout_defaults(symmetry):
"Scale",
"Fill Sides",
"Fill Smooth",
"Seam Smooth",
]
assert tuple(mirror_inputs[1].default_value) == (0, 0, 1)
assert mirror_inputs[3].default_value == 1
@@ -219,7 +220,7 @@ def test_disabled_fill_keeps_the_welded_seam(symmetry):
assert not any(plane[edge[0]] and plane[edge[1]] for edge in open_edges)
def test_fill_smooth_only_relaxes_the_seam(symmetry):
def test_fill_smooth_relaxes_the_junction_band(symmetry):
obj, _cutout, _mirror, _cutout_setter, mirror_setter = symmetry
mirror_setter("Fill Sides", True)
mirror_setter("Fill Smooth", 0)
@@ -232,17 +233,67 @@ def test_fill_smooth_only_relaxes_the_seam(symmetry):
plane = np.abs(points[:, 0]) <= tolerance
assert plane.any()
assert not any(all(plane[index] for index in face) for face in faces)
plain_interior = _front_vertices(
plain_points, plain_faces, plain_marker, lambda point: point[0] > tolerance
assert smooth_faces == plain_faces
axis = np.abs(plain_points[:, 0]) <= tolerance
moved = np.linalg.norm(smooth_points - plain_points, axis=1)
# 衔接带同时覆盖对称轴上的点与外侧轮廓。
assert moved[axis].max() > 1e-4
plain_rim = _positions(_front_rim(plain_points, plain_faces, plain_marker, tolerance))
smooth_rim = _positions(_front_rim(smooth_points, smooth_faces, smooth_marker, tolerance))
assert plain_rim != smooth_rim
def test_fill_smooth_band_reaches_beyond_two_rings():
"""Fill Smooth 平滑的是衔接带,覆盖范围比原先的两圈更宽。"""
from tools.nodes.common.depth_surface import _math
from tools.nodes.groups.image_cutout_symmetry import FILL_SMOOTH_RINGS, _falloff
bpy.ops.wm.read_factory_settings(use_empty=True)
size, rows = FILL_SMOOTH_RINGS + 4, 3
points = [(x, 0.0, y) for y in range(rows) for x in range(size)]
faces = [
(
y * size + x,
y * size + x + 1,
(y + 1) * size + x + 1,
(y + 1) * size + x,
)
for y in range(rows - 1)
for x in range(size - 1)
]
mesh = bpy.data.meshes.new("Fill band")
mesh.from_pydata(points, [], faces)
obj = bpy.data.objects.new("Fill band", mesh)
bpy.context.collection.objects.link(obj)
group = bpy.data.node_groups.new("Fill band", "GeometryNodeTree")
for direction in ("INPUT", "OUTPUT"):
group.interface.new_socket(
name="Geometry", in_out=direction, socket_type="NodeSocketGeometry"
)
source = group.nodes.new("NodeGroupInput")
position = group.nodes.new("GeometryNodeInputPosition")
axes = group.nodes.new("ShaderNodeSeparateXYZ")
group.links.new(position.outputs[0], axes.inputs[0])
outline = _math(group, "COMPARE", axes.outputs["X"], 0)
store = group.nodes.new("GeometryNodeStoreNamedAttribute")
store.data_type, store.domain = "FLOAT", "POINT"
store.inputs["Name"].default_value = "fill_band"
group.links.new(source.outputs["Geometry"], store.inputs["Geometry"])
group.links.new(_falloff(group, outline, FILL_SMOOTH_RINGS), store.inputs["Value"])
group.links.new(
store.outputs["Geometry"], group.nodes.new("NodeGroupOutput").inputs["Geometry"]
)
smooth_interior = _front_vertices(
smooth_points, smooth_faces, smooth_marker, lambda point: point[0] > tolerance
)
assert _positions(plain_interior) == _positions(smooth_interior)
plain_rim = _front_rim(plain_points, plain_faces, plain_marker, tolerance)
smooth_rim = _front_rim(smooth_points, smooth_faces, smooth_marker, tolerance)
assert plain_rim.shape == smooth_rim.shape
assert not np.allclose(np.sort(plain_rim[:, 1:], axis=0), np.sort(smooth_rim[:, 1:], axis=0))
obj.modifiers.new("Fill band", "NODES").node_group = group
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
data = result.to_mesh()
try:
band = np.array(
[item.value for item in data.attributes["fill_band"].data]
).reshape(rows, size)
finally:
result.to_mesh_clear()
assert (band[:, 3] > 0).all()
assert (band[:, FILL_SMOOTH_RINGS + 1] == 0).all()
def test_object_normal_attributes_follow_final_orientation(symmetry):