Refactor depth node groups and simplify options

- Removed `Smooth Weight` input from `O Image Depth Plane`, `O Image Depth Cutout`, and `O Image Depth Panorama` to streamline the interface and maintain consistent boundary smoothing behavior.
- Adjusted the `Reference Depth` input to appear last in the options panel for better organization, reflecting its role as a calibration value.
- Consolidated the `expand_smooth()` function to only accept necessary parameters, eliminating unused branches related to `Smooth Weight` and `pin_sharp`.
- Updated related tests and node assets to ensure functionality remains intact and consistent with the new structure.
- Introduced new specifications to document the changes and ensure compliance with the updated requirements.
This commit is contained in:
2026-09-16 13:12:12 +08:00
parent 6ca084ee4a
commit 7f69630371
26 changed files with 741 additions and 178 deletions

View File

@@ -9,7 +9,7 @@ from scipy.spatial import cKDTree
from anyimage.common.object import modifier_input_identifier, set_modifier_input
from anyimage.operators.cutout_tool.object import load_cutout_node_group
from tests.support.depth_surface import assert_closed, evaluated
from tests.support.depth_surface import assert_closed, assert_single_vertex_fans, evaluated
from tools.nodes.common.normal_map import AXIS_ATTRIBUTE, FACE_ATTRIBUTE, ROTATION_ATTRIBUTE
from tools.nodes.groups.image_depth_cutout import build_image_depth_cutout_group
from tools.nodes.groups.image_cutout_symmetry import build_image_cutout_symmetry_group
@@ -100,6 +100,7 @@ def symmetry(request):
def test_symmetry_interface_and_cutout_defaults(symmetry):
_obj, cutout, mirror, _cutout_setter, _mirror_setter = symmetry
assert not any(node.bl_idname == "GeometryNodeMeshBoolean" for node in mirror.node_group.nodes)
mirror_inputs = [
item
for item in mirror.node_group.interface.items_tree
@@ -146,6 +147,102 @@ def test_final_symmetry_axis_is_x_and_z_is_up(symmetry):
assert cKDTree(points).query(points * (-1, 1, 1))[0].max() < 1e-5
assert np.ptp(points[:, 2]) > 1.0
assert_closed(faces)
assert_single_vertex_fans(faces)
def _tolerance(points):
size = float(np.linalg.norm(points.max(axis=0) - points.min(axis=0)))
return max(size, 1e-8) * 1e-7
def _evaluated(obj):
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
mesh = result.to_mesh()
try:
points = np.array([vertex.co[:] for vertex in mesh.vertices])
faces = [tuple(polygon.vertices) for polygon in mesh.polygons]
marker = np.array([item.value for item in mesh.attributes[FACE_ATTRIBUTE].data])
finally:
result.to_mesh_clear()
return points, faces, marker
def _open_edges(faces):
counts = Counter(
tuple(sorted((a, b))) for f in faces for a, b in zip(f, (*f[1:], f[0]))
)
return [edge for edge, count in counts.items() if count == 1]
def _marked_vertices(points, faces, marker, value):
indices = {index for face, item in zip(faces, marker) if item == value for index in face}
return points[sorted(indices)]
def _front_vertices(points, faces, marker, predicate):
return np.array(
[point for point in _marked_vertices(points, faces, marker, 1) if predicate(point)]
)
def _positions(points):
return {tuple(np.round(point, 6)) for point in points}
def _front_rim(points, faces, marker, tolerance):
return _front_vertices(points, faces, marker, lambda point: abs(point[0]) <= tolerance)
def test_seam_clamps_geometry_onto_the_symmetry_plane(symmetry):
obj, _cutout, _mirror, _cutout_setter, _mirror_setter = symmetry
points, faces, marker = _evaluated(obj)
tolerance = _tolerance(points)
plane = np.abs(points[:, 0]) <= tolerance
assert _marked_vertices(points, faces, marker, 1)[:, 0].min() >= -tolerance
assert _marked_vertices(points, faces, marker, 2)[:, 0].max() <= tolerance
assert plane.any()
assert not any(all(plane[index] for index in face) for face in faces)
# Kept straddling faces fold onto the plane instead of being discarded.
assert any(plane[list(face)].any() and not plane[list(face)].all() for face in faces)
def test_disabled_fill_keeps_the_welded_seam(symmetry):
obj, _cutout, _mirror, _cutout_setter, mirror_setter = symmetry
mirror_setter("Fill Sides", False)
points, faces = evaluated(obj)
tolerance = _tolerance(points)
plane = np.abs(points[:, 0]) <= tolerance
open_edges = _open_edges(faces)
assert open_edges
assert not any(plane[edge[0]] and plane[edge[1]] for edge in open_edges)
def test_fill_smooth_only_relaxes_the_seam(symmetry):
obj, _cutout, _mirror, _cutout_setter, mirror_setter = symmetry
mirror_setter("Fill Sides", True)
mirror_setter("Fill Smooth", 0)
plain_points, plain_faces, plain_marker = _evaluated(obj)
mirror_setter("Fill Smooth", 4)
smooth_points, smooth_faces, smooth_marker = _evaluated(obj)
tolerance = _tolerance(plain_points)
for points, faces in ((plain_points, plain_faces), (smooth_points, smooth_faces)):
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
)
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))
def test_object_normal_attributes_follow_final_orientation(symmetry):