- Introduced a new `Boundary Padding` preference in the Cutout Tool with a default of 2 px and a configurable range of 1-4 px. - Updated the Cutout client to establish inward mapping from the final Mask, sampling in a fixed direction and processing independent material RGBA and Depth. - Enhanced the handling of RGBA and Depth during cutout operations, ensuring proper mapping and preservation of source data. - Added tests to validate the new boundary padding functionality and its integration with existing cutout processes. feat(geometry): remove overall mesh smoothing from geometry nodes - Removed the `Smooth` input from several geometry node groups to eliminate unnecessary overall mesh smoothing. - Retained `Smooth Weight` for depth nodes while removing it from Cutout and Relief Plane nodes. - Ensured that boundary, normal, and fill smoothing behaviors remain intact and consistent. - Updated tests and documentation to reflect the changes in node interfaces and behaviors. feat(normals): scale tangent normals with depth - Reused the existing `o_normal_reduction` attribute to adjust Tangent Normal strength based on the geometry's `Depth Scale`. - Ensured that Tangent Normal strength is consistent with the geometry depth, preserving full strength for materials that do not provide the attribute. - Updated tests to verify the correct behavior of normal strength adjustments in both Tangent and Object Space.
66 lines
3.2 KiB
Python
66 lines
3.2 KiB
Python
from collections import Counter
|
|
|
|
import bpy
|
|
|
|
import numpy as np
|
|
import pytest
|
|
from tools.nodes.groups.image_cutout import build_image_cutout_group
|
|
from tests.support.nodes import CUTOUT_BUILDERS
|
|
from anyimage.operators.cutout_tool.object import load_cutout_node_group
|
|
|
|
|
|
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
|
|
|
|
|
@pytest.mark.parametrize("source", ("definition", "asset"))
|
|
@pytest.mark.parametrize("shape", ("SOLID", "DEPTH_SOLID"))
|
|
def test_depth_axis_is_visible_in_options(shape, source):
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
group = CUTOUT_BUILDERS[shape]() if source == "definition" else load_cutout_node_group(shape)
|
|
inputs = {item.name: item for item in group.interface.items_tree
|
|
if item.item_type == "SOCKET" and item.in_out == "INPUT"}
|
|
axis = inputs["Depth Axis"]
|
|
assert axis.parent.name == "Options"
|
|
assert axis.menu_expanded
|
|
assert not axis.hide_in_modifier
|
|
assert axis.default_value == "-Z"
|
|
assert "Inward Axis" not in inputs
|
|
options = [item.name for item in inputs.values() if item.parent == axis.parent]
|
|
predecessor = "Cleanup Threshold" if shape == "SOLID" else "Reference Depth"
|
|
assert options[options.index(predecessor) + 1] == "Depth Axis"
|
|
if shape == "SOLID":
|
|
assert not any(item.item_type == "PANEL" and item.name == "Data" for item in group.interface.items_tree)
|
|
|
|
|
|
def test_cutout_thickness_modes():
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
mesh = bpy.data.meshes.new("Profile")
|
|
mesh.from_pydata([(-1,-1,0), (1,-1,0), (1,1,0), (-1,1,0), (0,0,0)], [], [(0,1,4), (1,2,4), (2,3,4), (3,0,4)])
|
|
profile = mesh.attributes.new("o_balloon", "FLOAT", "POINT")
|
|
profile.data.foreach_set("value", [0,0,0,0,0.6])
|
|
obj = bpy.data.objects.new("Profile", mesh)
|
|
bpy.context.collection.objects.link(obj)
|
|
modifier = obj.modifiers.new("Cutout", "NODES")
|
|
modifier.node_group = build_image_cutout_group()
|
|
for mode, thickness in ((0, 0), (0, 1), (1, 0), (1, 0.4)):
|
|
for name, value in (("Mode", mode), ("Thickness", thickness)):
|
|
subtype = ("DISTANCE" if mode else "NONE") if name == "Thickness" else None
|
|
set_modifier_input(modifier, modifier_input_identifier(modifier.node_group, name, subtype=subtype), value)
|
|
obj.update_tag(refresh={"DATA"})
|
|
bpy.context.view_layer.update()
|
|
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
|
result = evaluated.to_mesh()
|
|
try:
|
|
z = np.array([vertex.co.z for vertex in result.vertices])
|
|
assert np.isfinite(z).all() and len(z)
|
|
if thickness == 0:
|
|
assert len(result.polygons) == 4 and np.max(abs(z)) < 1e-7
|
|
assert all(abs(item.value) < 1e-7 for item in result.attributes["o_normal_reduction"].data)
|
|
else:
|
|
back, front = (-0.6, 0.6) if mode == 0 else (-thickness, 0.0)
|
|
assert np.isclose(z.min(), back) and np.isclose(z.max(), front)
|
|
uses = Counter(tuple(sorted((a,b))) for face in result.polygons for a,b in zip(face.vertices, (*face.vertices[1:],face.vertices[0])))
|
|
assert set(uses.values()) == {2}
|
|
finally:
|
|
evaluated.to_mesh_clear()
|