Refactor comparison operations in geometry nodes
- Introduced a new `compare_node` function to streamline comparison operations across various geometry nodes. - Replaced instances of mathematical comparison nodes with the new `compare_node` function in multiple files, enhancing code readability and maintainability. - Updated related logic in cutout, depth surface, and image processing groups to utilize the new comparison method. - Ensured consistent handling of boolean logic through the introduction of a `boolean_node` function for combining boolean inputs.
This commit is contained in:
@@ -37,9 +37,21 @@ def test_camera_projection_and_reference_depth(surface, strength):
|
||||
base = evaluated(obj)
|
||||
uv = (base[0][:, [0, 2]] + (2, 1)) / (4, 2)
|
||||
texel = np.clip(uv * (16, 8), (0.5, 0.5), (15.5, 7.5))
|
||||
camera = np.column_stack(((texel[:, 0] - 5) / 8,
|
||||
(2 + texel[:, 0] / 16) * 0.5 - 1.2,
|
||||
(texel[:, 1] - 3) / 8))
|
||||
depth = 2 + texel[:, 0] / 16
|
||||
# Outline vertices keep the border ray and adopt the adjacent face-centre
|
||||
# depth, the same rule cut edges use to inherit their face sample.
|
||||
face_depth = np.zeros((len(base[0]), 2))
|
||||
for face in base[1]:
|
||||
center = np.mean(base[0][list(face)], axis=0)
|
||||
value = 2 + np.clip((center[0] + 2) / 4 * 16, 0.5, 15.5) / 16
|
||||
for index in face:
|
||||
face_depth[index] += (value, 1)
|
||||
face_depth = face_depth[:, 0] / face_depth[:, 1]
|
||||
on_outline = np.isclose(uv, 0).any(axis=1) | np.isclose(uv, 1).any(axis=1)
|
||||
ratio = np.where(on_outline, face_depth / depth, 1.0)
|
||||
camera = np.column_stack(((texel[:, 0] - 5) / 8 * ratio,
|
||||
(depth * 0.5) * ratio - 1.2,
|
||||
(texel[:, 1] - 3) / 8 * ratio))
|
||||
expected = base[0] * (1 - strength) + camera * strength
|
||||
set_value("Depth Scale", float(strength))
|
||||
projected = evaluated(obj)
|
||||
@@ -297,7 +309,7 @@ def test_relief_direction_adjusts_depth_and_keeps_base(relief, thickness, direct
|
||||
("valid", 1.0), ("invalid", 0.0), ("invalid", 0.9),
|
||||
("partial", 0.4), ("partial", 0.9), ("half", 0.9), ("stripe", 0.9),
|
||||
])
|
||||
def test_valid_only_deletes_invalid_points_and_wire_edges(surface, pattern, threshold):
|
||||
def test_valid_only_keeps_faces_covering_valid_samples(surface, pattern, threshold):
|
||||
obj, set_value, _, inputs, _ = surface
|
||||
set_value("Mask Threshold", threshold)
|
||||
depth_image = obj.modifiers[0][inputs["Depth Image"].identifier]
|
||||
@@ -315,13 +327,16 @@ def test_valid_only_deletes_invalid_points_and_wire_edges(surface, pattern, thre
|
||||
pixels[..., 3] = alpha
|
||||
depth_image.pixels.foreach_set(pixels.ravel())
|
||||
depth_image.update()
|
||||
|
||||
def sampled(x):
|
||||
return np.interp((x + 2) / 4 * 16 - 0.5, np.arange(16), alpha)
|
||||
|
||||
for subdivisions in (0, 2, 5):
|
||||
set_value("Subdivide", subdivisions)
|
||||
set_value("Depth Mask", False)
|
||||
base = evaluated(obj)
|
||||
u = (base[0][:, 0] + 2) / 4
|
||||
sampled = np.interp(u * 16 - 0.5, np.arange(16), alpha)
|
||||
expected = [face for face in base[1] if np.all(sampled[list(face)] >= threshold)]
|
||||
expected = [face for face in base[1]
|
||||
if sampled(np.mean(base[0][list(face)][:, 0])) >= threshold]
|
||||
set_value("Depth Mask", True)
|
||||
culled = evaluated(obj)
|
||||
assert len(culled[1]) == len(expected)
|
||||
|
||||
@@ -78,6 +78,19 @@ def test_depth_alpha_culls_faces_without_double_multiplication():
|
||||
assert len(evaluated(obj)[1]) == 384
|
||||
|
||||
|
||||
def test_alpha_cut_keeps_the_ring_sharing_the_face_centre_contour():
|
||||
"""Faces are judged at their centre, so the ring straddling the sky stays."""
|
||||
alpha = np.ones((64, 128))
|
||||
alpha[:, 64:] = 0
|
||||
obj, _ = panorama(3.0, alpha, subdivide=4)
|
||||
points, faces, _ = evaluated(obj)
|
||||
plain, _ = panorama(3.0, 1.0, subdivide=4)
|
||||
_, full, _ = evaluated(plain)
|
||||
# Judging faces by their corners would drop one more ring of the half sphere.
|
||||
assert len(faces) == len(full) // 2
|
||||
np.testing.assert_allclose(np.linalg.norm(points, axis=1), 3, atol=1e-5)
|
||||
|
||||
|
||||
def test_invalid_depth_does_not_bleed_into_alpha_boundary():
|
||||
alpha = np.ones((64, 128))
|
||||
alpha[:, 64:] = 0
|
||||
|
||||
@@ -66,6 +66,9 @@ def test_saved_parameter_descriptions_match_definitions():
|
||||
}:
|
||||
assert "Smooth" not in input_names
|
||||
assert "Smooth Weight" not in input_names
|
||||
if "Mask Threshold" in parameters[group.name]:
|
||||
threshold = next(item for item in input_items if item.name == "Mask Threshold")
|
||||
assert (threshold.min_value, threshold.max_value) == pytest.approx((0.0, 0.99))
|
||||
if group.name in {
|
||||
"O Image Depth Cutout", "O Image Depth Plane", "O Image Relief Plane",
|
||||
}:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"""Verify point culling removes its dangling wire geometry."""
|
||||
"""Verify face culling removes the faces the depth mask excludes."""
|
||||
|
||||
import bpy
|
||||
|
||||
from tools.nodes.common.nodes import interface_socket
|
||||
from tools.nodes.common.validity import delete_invalid_points
|
||||
from tools.nodes.common.nodes import compare_node, interface_socket
|
||||
from tools.nodes.common.validity import delete_invalid_faces
|
||||
|
||||
|
||||
def test_point_culling_removes_wire_edges_without_removing_adjacent_surface():
|
||||
def test_face_culling_removes_invalid_faces_without_leaving_wire_geometry():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
mesh = bpy.data.meshes.new("Two Quads")
|
||||
mesh.from_pydata([(0, 0, 0), (1, 0, 0), (2, 0, 0),
|
||||
@@ -14,17 +14,14 @@ def test_point_culling_removes_wire_edges_without_removing_adjacent_surface():
|
||||
[(0, 1, 4, 3), (1, 2, 5, 4)])
|
||||
obj = bpy.data.objects.new("Surface", mesh)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
group = bpy.data.node_groups.new("Point Culling", "GeometryNodeTree")
|
||||
group = bpy.data.node_groups.new("Face Culling", "GeometryNodeTree")
|
||||
interface_socket(group, "Geometry", "INPUT", "NodeSocketGeometry")
|
||||
interface_socket(group, "Geometry", "OUTPUT", "NodeSocketGeometry")
|
||||
inlet = group.nodes.new("NodeGroupInput")
|
||||
outlet = group.nodes.new("NodeGroupOutput")
|
||||
index = group.nodes.new("GeometryNodeInputIndex")
|
||||
invalid = group.nodes.new("ShaderNodeMath")
|
||||
invalid.operation = "LESS_THAN"
|
||||
invalid.inputs[1].default_value = 1
|
||||
group.links.new(index.outputs[0], invalid.inputs[0])
|
||||
geometry = delete_invalid_points(group, inlet.outputs["Geometry"], invalid.outputs[0])
|
||||
invalid = compare_node(group, "LESS_THAN", index.outputs["Index"], 1, data_type="INT")
|
||||
geometry = delete_invalid_faces(group, inlet.outputs["Geometry"], invalid)
|
||||
group.links.new(geometry, outlet.inputs["Geometry"])
|
||||
modifier = obj.modifiers.new("Cull", "NODES")
|
||||
modifier.node_group = group
|
||||
|
||||
Reference in New Issue
Block a user