Implement arrangement framework for node groups
- Introduced `frames.py` to manage field complex calculations and their arrangement within node groups. - Added functionality to create and remove field frames based on node arrangements. - Developed `passages.py` to find unobstructed paths for connections around calculation frames. - Implemented `references.py` to route shared results on peripheral tracks, ensuring efficient distribution of outputs. - Created `sections.py` to reserve space for field calculations adjacent to geometry consumers. - Added `sources.py` to position shared calculation sources effectively beside their distribution channels. - Enhanced outline smoothing with `outline_smoothing.py` to adapt camera depth along original outlines.
This commit is contained in:
@@ -66,7 +66,7 @@ def test_surface_caps_and_segmented_walls_are_connected(thickness, split):
|
||||
@pytest.mark.parametrize("size", [0.01, 1.0, 100.0])
|
||||
def test_split_mapping_and_uniform_scale(size):
|
||||
obj, set_value = surface(step=2.0, size=size)
|
||||
# Face centers are size/2 apart: gradient is 4.
|
||||
# The normalized ray jump crosses the threshold only at full strength.
|
||||
counts = []
|
||||
for split in (0.0, 0.25, 0.5, 1.0):
|
||||
set_value("Split Threshold", split)
|
||||
@@ -80,12 +80,12 @@ def test_split_mapping_and_uniform_scale(size):
|
||||
assert len(coords) == 18 and np.abs(coords[:, 2]).max() < 1e-6
|
||||
|
||||
|
||||
def test_midpoint_selects_gradient_six_and_preserves_faces():
|
||||
obj, set_value = surface(step=3.0)
|
||||
def test_midpoint_selects_relative_ray_jump_and_preserves_faces():
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Split Threshold", 0.5)
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == 18 and len(faces) == 8
|
||||
assert set(np.round(coords[:, 2], 4)) == {0.0, -3.0}
|
||||
assert set(np.round(coords[:, 2], 4)) == {2.0, -2.0}
|
||||
|
||||
|
||||
def test_cleanup_keeps_split_parts_of_a_large_source_island():
|
||||
@@ -102,7 +102,7 @@ def test_cleanup_keeps_split_parts_of_a_large_source_island():
|
||||
|
||||
@pytest.mark.parametrize("depth_scale", [0.0, 0.25, 1.0, 2.0, 4.0])
|
||||
def test_split_topology_uses_depth_before_display_scaling(depth_scale):
|
||||
obj, set_value = surface(step=3.0)
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Split Threshold", 0.5)
|
||||
original, original_faces = evaluated(obj)
|
||||
set_value("Depth Scale", depth_scale)
|
||||
@@ -203,7 +203,7 @@ def test_small_positive_split_preserves_unsplit_projection():
|
||||
|
||||
|
||||
def test_cut_vertices_preserve_camera_rays_and_other_vertices():
|
||||
obj, set_value = surface(step=3.0)
|
||||
obj, set_value = surface(step=4.0)
|
||||
original, original_faces = evaluated(obj)
|
||||
set_value("Split Threshold", 0.5)
|
||||
coords, faces = evaluated(obj)
|
||||
@@ -212,14 +212,14 @@ def test_cut_vertices_preserve_camera_rays_and_other_vertices():
|
||||
before, after = original[source_index], coords[result_index]
|
||||
if obj.data.vertices[source_index].co.x != 1.0:
|
||||
np.testing.assert_allclose(after, before, atol=1e-6)
|
||||
before_depth, after_depth = 1.0 - before[2], 1.0 - after[2]
|
||||
before_depth, after_depth = 3.0 - before[2], 3.0 - after[2]
|
||||
np.testing.assert_allclose(
|
||||
after[:2] / after_depth, before[:2] / before_depth, atol=1e-6
|
||||
)
|
||||
|
||||
|
||||
def test_source_surface_sampling_preserves_depth_on_side_walls():
|
||||
obj, set_value = surface(step=3.0)
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Thickness", 0.1)
|
||||
coords, faces = evaluated(obj)
|
||||
@@ -286,7 +286,7 @@ def test_zero_center_distance_does_not_split_or_produce_nan():
|
||||
|
||||
|
||||
def test_triangle_corner_weights_do_not_open_sloped_side_walls():
|
||||
obj, set_value = surface(step=3.0)
|
||||
obj, set_value = surface(step=4.0)
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
bmesh.ops.triangulate(bm, faces=list(bm.faces))
|
||||
@@ -305,7 +305,7 @@ def test_triangle_corner_weights_do_not_open_sloped_side_walls():
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resolution", [1, 2, 4])
|
||||
def test_linear_depth_slope_has_density_independent_selection(resolution):
|
||||
def test_relative_distance_slope_has_density_independent_selection(resolution):
|
||||
obj, set_value = surface(step=0.0, resolution=resolution)
|
||||
output_split_mesh(obj)
|
||||
bm = bmesh.new()
|
||||
@@ -315,10 +315,65 @@ def test_linear_depth_slope_has_density_independent_selection(resolution):
|
||||
bm.free()
|
||||
image = bpy.data.images["Camera"]
|
||||
pixels = np.array(image.pixels[:], np.float32).reshape(32, 64, 4)
|
||||
pixels[..., 2] = 1 + 4 * pixels[..., 0]
|
||||
pixels[..., 2] = np.exp(6 * pixels[..., 0])
|
||||
pixels[..., :2] = 0
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Split Threshold", 0.5)
|
||||
set_value("Split Threshold", 0.25)
|
||||
assert len(evaluated(obj)[0]) == len(obj.data.vertices)
|
||||
set_value("Split Threshold", 1.0)
|
||||
assert len(evaluated(obj)[0]) > len(obj.data.vertices)
|
||||
|
||||
@pytest.mark.parametrize("kind", ["plane", "cutout"])
|
||||
@pytest.mark.parametrize("source", ["definition", "asset"])
|
||||
def test_split_uses_ray_distance_and_ignores_absolute_distance_scale(kind, source):
|
||||
from tests.support.planes import create_surface
|
||||
|
||||
if kind == "plane":
|
||||
obj, set_value, _, _, image = create_surface(source, "DEPTH")
|
||||
set_value("Subdivide", 1)
|
||||
else:
|
||||
obj, set_value = surface(step=0)
|
||||
if source == "asset":
|
||||
from tests.support.paths import PROJECT_ROOT
|
||||
with bpy.data.libraries.load(str(PROJECT_ROOT / "src/anyimage/assets/O_AnyImage.blend")) as (_, target):
|
||||
target.node_groups = ["O Image Depth Cutout"]
|
||||
obj.modifiers[0].node_group = target.node_groups[0]
|
||||
image = bpy.data.images["Camera"]
|
||||
output_split_mesh(obj)
|
||||
set_value("Reference Depth", 3)
|
||||
set_value("Split Threshold", 0)
|
||||
width, height = image.size
|
||||
pixels = np.ones((height, width, 4), np.float32)
|
||||
original_count = len(evaluated(obj)[0])
|
||||
set_value("Split Threshold", 1)
|
||||
# Equal axial Z, but a tenfold ray-distance jump: Z-only splitting misses it.
|
||||
pixels[..., :3] = (0, 0, 1)
|
||||
pixels[:, width // 2:, 0] = np.sqrt(99)
|
||||
signature = None
|
||||
for scale in (0.01, 1.0, 100.0):
|
||||
scaled = pixels.copy()
|
||||
scaled[..., :3] *= scale
|
||||
image.pixels.foreach_set(scaled.ravel())
|
||||
image.update()
|
||||
set_value("Depth Image", image)
|
||||
points, faces = evaluated(obj)
|
||||
assert len(points) > original_count
|
||||
if signature is None:
|
||||
signature = points, faces
|
||||
np.testing.assert_array_equal(points, signature[0])
|
||||
assert faces == signature[1]
|
||||
# Equal ray distances with different Z must keep the faces connected.
|
||||
pixels[..., :3] = (0, 0, 10)
|
||||
pixels[:, width // 2:, :3] = (8, 0, 6)
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Depth Image", image)
|
||||
assert len(evaluated(obj)[0]) == original_count
|
||||
# Degenerate distances and a zero reference remain finite and connected.
|
||||
pixels[..., :3] = 0
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Reference Depth", 0)
|
||||
points, _ = evaluated(obj)
|
||||
assert len(points) == original_count and np.isfinite(points).all()
|
||||
|
||||
Reference in New Issue
Block a user