chore: bump version to 1.0.1 and update related files

This commit is contained in:
2026-09-18 08:36:51 +08:00
parent 0e0287d340
commit 531f7a4dfb
10 changed files with 7 additions and 554 deletions

View File

@@ -5,69 +5,8 @@ from collections import Counter
import bpy
import bmesh
import numpy as np
import pytest
from tests.support.depth_surface import surface, evaluated, assert_closed, component_count
from tools.nodes.common.boundary_smoothing import cut_boundary_influence
from tools.nodes.common.smoothing import pinned_smooth
def pinned_group(
iterations=1,
region=1.0,
weight=1.0,
pin_boundary=True,
pin_sharp=True,
):
group = bpy.data.node_groups.new("Pinned smooth", "GeometryNodeTree")
for direction in ("INPUT", "OUTPUT"):
group.interface.new_socket(
name="Geometry", in_out=direction, socket_type="NodeSocketGeometry"
)
smooth = group.interface.new_socket(
name="Smooth", in_out="INPUT", socket_type="NodeSocketInt"
)
smooth.default_value = iterations
source = group.nodes.new("NodeGroupInput")
control = group.nodes.new("NodeGroupInput")
region_value = group.nodes.new("ShaderNodeValue")
region_value.outputs[0].default_value = region
result = pinned_smooth(
group,
source.outputs["Geometry"],
control.outputs["Smooth"],
region_value.outputs[0],
weight=weight,
pin_boundary=pin_boundary,
pin_sharp=pin_sharp,
)
group.links.new(result, group.nodes.new("NodeGroupOutput").inputs["Geometry"])
return group
def pinned_result(points, faces, **options):
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("Pinned")
mesh.from_pydata(points, [], faces)
obj = bpy.data.objects.new("Pinned", mesh)
bpy.context.collection.objects.link(obj)
obj.modifiers.new("Pinned", "NODES").node_group = pinned_group(**options)
return evaluated(obj)[0]
def grid(size):
points = [(x, y, 0.0) for y in range(size) 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(size - 1)
for x in range(size - 1)
]
return points, faces
from tests.support.depth_surface import surface, evaluated, assert_closed
def diagonal_surface(triangles=False):
@@ -113,60 +52,6 @@ def edge_band(faces, seeds, rings=2):
return selected
@pytest.mark.parametrize(("outline_scale", "expected_outline"), [(0.1, 0.1), (1.0, 1.0)])
def test_split_gets_full_influence_and_outline_uses_configured_scale(outline_scale, expected_outline):
bpy.ops.wm.read_factory_settings(use_empty=True)
points, faces, split = [], [], []
for side in range(2):
offset = len(points)
for y in range(9):
for x in range(5):
points.append((side * 4 + x, y, 0))
split.append(x == (4 if side == 0 else 0))
faces.extend(
(
offset + y * 5 + x,
offset + y * 5 + x + 1,
offset + (y + 1) * 5 + x + 1,
offset + (y + 1) * 5 + x,
)
for y in range(8)
for x in range(4)
)
mesh = bpy.data.meshes.new("Boundary influence")
mesh.from_pydata(points, [], faces)
mesh.attributes.new("test_split", "BOOLEAN", "POINT").data.foreach_set("value", split)
obj = bpy.data.objects.new("Boundary influence", mesh)
bpy.context.collection.objects.link(obj)
group = bpy.data.node_groups.new("Boundary influence", "GeometryNodeTree")
for direction in ("INPUT", "OUTPUT"):
group.interface.new_socket(name="Geometry", in_out=direction, socket_type="NodeSocketGeometry")
source = group.nodes.new("NodeGroupInput")
split_attribute = group.nodes.new("GeometryNodeInputNamedAttribute")
split_attribute.data_type = "BOOLEAN"
split_attribute.inputs["Name"].default_value = "test_split"
store = group.nodes.new("GeometryNodeStoreNamedAttribute")
store.data_type, store.domain = "FLOAT", "POINT"
store.inputs["Name"].default_value = "test_influence"
group.links.new(source.outputs["Geometry"], store.inputs["Geometry"])
group.links.new(
cut_boundary_influence(group, split_attribute.outputs["Attribute"], outline_scale=outline_scale),
store.inputs["Value"],
)
output = group.nodes.new("NodeGroupOutput")
group.links.new(store.outputs["Geometry"], output.inputs["Geometry"])
obj.modifiers.new("Boundary influence", "NODES").node_group = group
evaluated_obj = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
result = evaluated_obj.to_mesh()
try:
influence = np.array([value.value for value in result.attributes["test_influence"].data])
finally:
evaluated_obj.to_mesh_clear()
np.testing.assert_allclose(influence[4 * 5], expected_outline, atol=1e-6)
np.testing.assert_allclose(influence[4 * 5 + 4], 1.0, atol=1e-6)
np.testing.assert_allclose(influence[4], 1.0, atol=1e-6)
def test_cutout_boundary_smooth_defaults_to_four():
obj, _ = surface()
socket = next(
@@ -222,30 +107,6 @@ def test_smoothing_without_split_moves_outline_only_within_boundary_band():
np.testing.assert_array_equal(evaluated(obj)[0], original)
def test_smoothed_cut_shells_stay_closed_and_separate():
mode = 1
from anyimage.common.object import modifier_input_identifier, set_modifier_input
obj, set_value = diagonal_surface(triangles=True)
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set(
"value", np.full(len(obj.data.vertices), 0.3, dtype=np.float32)
)
set_value("Depth Scale", 1)
set_value("Boundary Smooth", 4)
original, faces = evaluated(obj)
count = component_count(faces)
set_value("Mode", mode)
modifier = obj.modifiers[0]
identifier = modifier_input_identifier(modifier.node_group, "Thickness", subtype="NONE" if mode == 0 else "DISTANCE")
set_modifier_input(modifier, identifier, 0.05)
obj.update_tag(refresh={"DATA"})
bpy.context.view_layer.update()
result, shell_faces = evaluated(obj)
assert np.isfinite(result).all()
assert_closed(shell_faces)
assert component_count(shell_faces) == count
def test_panorama_smoothing_preserves_interior_and_uvs():
from tests.tools.nodes.test_image_depth_panorama import panorama, evaluated as panorama_mesh
@@ -346,50 +207,3 @@ def test_panorama_validity_cut_smoothing_stays_in_two_ring_band():
np.testing.assert_array_equal(result_uv, uv)
np.testing.assert_array_equal(after[outside], before[outside])
assert np.max(np.linalg.norm(after-before, axis=1)) > 1e-4
def test_boundary_points_follow_the_boundary_strip_blur():
"""A boundary spike relaxes along the outline instead of being pulled inward."""
points, faces = grid(4)
points[1] = (1.0, -0.5, 0.0)
result = pinned_result(points, faces)
# 边界条带只连接边界邻居:自身权重 1,两个边界邻居权重 0.5。
np.testing.assert_allclose(result[1], (1.0, -0.25, 0.0), atol=1e-6)
def test_sharp_features_lower_the_smoothing_weight():
"""A crease inside the normal blur range reduces the pinned displacement."""
points, faces = grid(3)
points[4] = (1.0, -0.5, 0.0)
flat = pinned_result(points, faces)
creased = list(points)
creased[0] = (0.0, 0.0, 1.0)
creased = pinned_result(creased, faces)
# 折痕不在尖点的邻居集合内,两次运行的平滑目标相同,只有锐度系数不同。
flat_step = abs(flat[4][1] + 0.5)
creased_step = abs(creased[4][1] + 0.5)
assert flat_step > 1e-3
assert creased_step < flat_step * 0.99
def test_junction_band_uses_the_neighbourhood_average():
"""Without the boundary pin a boundary spike follows every connected neighbour."""
points, faces = grid(4)
points[1] = (1.0, -0.5, 0.0)
result = pinned_result(points, faces, pin_boundary=False)
# 自身权重 1 与三个邻居的平均:(-0.5 + 0 + 0 + 1) / 4。
np.testing.assert_allclose(result[1], (1.0, 0.125, 0.0), atol=1e-6)
def test_junction_band_keeps_the_sharpness_weight():
"""The sharpness weight stays active without the boundary pin."""
points, faces = grid(3)
points[4] = (1.0, -0.5, 0.0)
flat = pinned_result(points, faces, pin_boundary=False)
creased = list(points)
creased[0] = (0.0, 0.0, 1.0)
creased = pinned_result(creased, faces, pin_boundary=False)
flat_step = abs(flat[4][1] + 0.5)
creased_step = abs(creased[4][1] + 0.5)
assert flat_step > 1e-3
assert creased_step < flat_step * 0.99