Optimize Cutout Geometry Nodes
- Refactor Depth Cutout to reuse the projected outline field, reducing redundant boundary calculations and improving performance. - Derive the center field from shared low-frequency fields, eliminating an unnecessary blur operation. - Implement triangle-only strip cleanup for Depth Cutout, as the input is confirmed to be triangulated. - Short-circuit zero-thickness branches in both Cutout and Depth Cutout to avoid unnecessary geometry generation. - Standardize internal attribute naming with a new `_o_` prefix and implement wildcard cleanup for temporary attributes. - Update relevant tests to ensure functionality remains consistent and validate changes against existing behavior.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
"""Contracts for smooth depth volume, editable presets and normal masks."""
|
||||
from collections import Counter
|
||||
import bpy
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from tools.nodes.groups.image_depth_cutout import build_volume_fields
|
||||
from tools.nodes.groups.image_depth_cutout import build_image_depth_cutout_group, build_volume_fields
|
||||
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
||||
|
||||
|
||||
@@ -191,7 +192,7 @@ def test_smooth_back_rejects_front_noise_and_preserves_center_depth():
|
||||
evaluated.to_mesh_clear()
|
||||
|
||||
|
||||
def test_depth_cutout_normal_map_only_affects_front_faces():
|
||||
def test_depth_cutout_normal_reduction_uses_point_domain():
|
||||
from tests.support.depth_surface import surface
|
||||
obj, set_value = surface()
|
||||
set_value("Thickness", 0.3)
|
||||
@@ -200,14 +201,77 @@ def test_depth_cutout_normal_map_only_affects_front_faces():
|
||||
mesh = evaluated.to_mesh()
|
||||
try:
|
||||
attribute = mesh.attributes["o_normal_reduction"]
|
||||
assert attribute.domain == "FACE"
|
||||
weights = [1.0 - item.value for item in attribute.data]
|
||||
assert weights.count(1) == len(obj.data.polygons)
|
||||
assert weights.count(0) == len(mesh.polygons) - len(obj.data.polygons)
|
||||
assert attribute.domain == "POINT"
|
||||
values = [item.value for item in attribute.data]
|
||||
assert values
|
||||
assert set(values) <= {0.0, 0.5, 1.0}
|
||||
assert 0.0 in values and 1.0 in values
|
||||
finally:
|
||||
evaluated.to_mesh_clear()
|
||||
|
||||
|
||||
def test_depth_cutout_reuses_boundary_and_slow_field_nodes():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
group = build_image_depth_cutout_group()
|
||||
counts = Counter(node.bl_idname for node in group.nodes)
|
||||
assert counts["GeometryNodeInputMeshEdgeNeighbors"] <= 15
|
||||
assert counts["GeometryNodeBlurAttribute"] <= 12
|
||||
|
||||
|
||||
def test_center_derives_from_shared_slow_fields():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
points = [(x, y) for y in range(3) for x in range(3)]
|
||||
mesh = bpy.data.meshes.new("Center fields")
|
||||
mesh.from_pydata(
|
||||
[(x, y, 0) for x, y in points], [],
|
||||
[(0, 1, 4, 3), (1, 2, 5, 4), (3, 4, 7, 6), (4, 5, 8, 7)],
|
||||
)
|
||||
profile = mesh.attributes.new("o_balloon", "FLOAT", "POINT")
|
||||
for value in profile.data:
|
||||
value.value = 0.2
|
||||
obj = bpy.data.objects.new("Center fields", mesh)
|
||||
bpy.context.collection.objects.link(obj)
|
||||
group = bpy.data.node_groups.new("Center derivation test", "GeometryNodeTree")
|
||||
group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
|
||||
group.interface.new_socket(name="Thickness", in_out="INPUT", socket_type="NodeSocketFloat").default_value = 1
|
||||
group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
|
||||
source = group.nodes.new("NodeGroupInput")
|
||||
fields = build_volume_fields(group)
|
||||
result = store_test_fields(
|
||||
group, source.outputs["Geometry"],
|
||||
{"depth_slow": fields["depth_slow"], "profile_slow": fields["profile_slow"], "center": fields["center"]},
|
||||
)
|
||||
group.links.new(result, group.nodes.new("NodeGroupOutput").inputs["Geometry"])
|
||||
obj.modifiers.new("Fields", "NODES").node_group = group
|
||||
bpy.context.view_layer.update()
|
||||
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
||||
result = evaluated.to_mesh()
|
||||
try:
|
||||
depth_slow = np.array([item.value for item in result.attributes["depth_slow"].data])
|
||||
profile_slow = np.array([item.value for item in result.attributes["profile_slow"].data])
|
||||
center = np.array([item.value for item in result.attributes["center"].data])
|
||||
np.testing.assert_allclose(center, depth_slow - profile_slow, atol=1e-5)
|
||||
finally:
|
||||
evaluated.to_mesh_clear()
|
||||
|
||||
|
||||
def test_zero_thickness_short_circuits_shell_without_warnings():
|
||||
from tests.support.depth_surface import surface, evaluated
|
||||
obj, set_value = surface()
|
||||
set_value("Thickness", 0.0)
|
||||
points, faces = evaluated(obj)
|
||||
assert len(faces) == len(obj.data.polygons)
|
||||
modifier = obj.modifiers[0]
|
||||
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
||||
mesh = result.to_mesh()
|
||||
try:
|
||||
assert not list(modifier.node_warnings), [
|
||||
warning.message for warning in modifier.node_warnings
|
||||
]
|
||||
finally:
|
||||
result.to_mesh_clear()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("thickness,inflation", [
|
||||
(0.0, 1.0), (0.02, 0.0), (0.02, 0.0001),
|
||||
(0.02, 0.5), (0.1, 0.5), (0.1, 1.0),
|
||||
|
||||
Reference in New Issue
Block a user