feat(cutout): implement boundary padding for cutout textures
- 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.
This commit is contained in:
@@ -62,7 +62,7 @@ def symmetry(request):
|
||||
cutout, mirror = setters
|
||||
mirror('Fill Smooth', 0)
|
||||
for name, value in [('Depth Image', image), ('Reference Depth', 1.45),
|
||||
('Depth Split', 0), ('Boundary Smooth', 0), ('Smooth', 0), ('Depth Axis', 0)]:
|
||||
('Depth Split', 0), ('Boundary Smooth', 0), ('Depth Axis', 0)]:
|
||||
cutout(name, value)
|
||||
yield obj, cutout, mirror
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
@@ -84,7 +84,7 @@ def test_zero_thickness_matches_original_projected(symmetry, mode):
|
||||
cutout('Mode', mode)
|
||||
baseline = json.loads((Path(__file__).parent / 'fixtures/projected_symmetry.json').read_text())
|
||||
for case in baseline['cases']:
|
||||
mirror('Symmetry Direction', case['direction'])
|
||||
mirror('Direction', case['direction'])
|
||||
mirror('Fill Sides', case['filled'])
|
||||
points, faces = evaluated(obj)
|
||||
expected = np.array(case['points'])
|
||||
@@ -99,7 +99,7 @@ def test_completed_geometry_is_mirrored_and_closed(symmetry, mode, smooth):
|
||||
obj, cutout, mirror = symmetry
|
||||
cutout('Mode', mode)
|
||||
cutout('Thickness', .2 if mode else .6, 'DISTANCE' if mode else 'NONE')
|
||||
mirror('Symmetry Direction', (.3, -.2, 1))
|
||||
mirror('Direction', (.3, -.2, 1))
|
||||
mirror('Fill Smooth', smooth)
|
||||
points, faces = evaluated(obj)
|
||||
assert len(points) and np.isfinite(points).all()
|
||||
@@ -150,25 +150,47 @@ def test_fill_smoothing_preserves_surfaces_and_only_moves_wall_interiors(symmetr
|
||||
|
||||
def test_direction_offset_and_empty_geometry(symmetry):
|
||||
obj, _, mirror = symmetry
|
||||
mirror('Depth Offset', 2.)
|
||||
mirror('Symmetry Direction', (.3, -.2, 1))
|
||||
mirror('Offset', 2.)
|
||||
mirror('Direction', (.3, -.2, 1))
|
||||
original, original_faces = evaluated(obj)
|
||||
mirror('Depth Offset', 2.2)
|
||||
mirror('Offset', 2.2)
|
||||
points, faces = evaluated(obj)
|
||||
assert faces == original_faces
|
||||
np.testing.assert_allclose(points[:, :2], original[:, :2], atol=1e-6)
|
||||
np.testing.assert_allclose(abs(points[:, 2]) - abs(original[:, 2]), .2, atol=1e-6)
|
||||
mirror('Depth Offset', -10.)
|
||||
mirror('Offset', -10.)
|
||||
points, faces = evaluated(obj)
|
||||
assert not len(points) and not faces
|
||||
|
||||
|
||||
def test_symmetry_scale_uses_aligned_axis(symmetry):
|
||||
obj, _, mirror = symmetry
|
||||
mirror('Direction', (.3, -.2, 1))
|
||||
mirror('Offset', .5)
|
||||
mirror('Fill Sides', False)
|
||||
original, original_faces = evaluated(obj)
|
||||
mirror('Scale', 2)
|
||||
points, faces = evaluated(obj)
|
||||
assert faces == original_faces
|
||||
np.testing.assert_allclose(points[:, :2], original[:, :2], atol=1e-6)
|
||||
np.testing.assert_allclose(points[:, 2], original[:, 2] * 2, atol=1e-6)
|
||||
|
||||
|
||||
def test_symmetry_interface_and_cutout_defaults(symmetry):
|
||||
obj, _, _ = symmetry
|
||||
inputs = [item for item in obj.modifiers[1].node_group.interface.items_tree
|
||||
if item.item_type == 'SOCKET' and item.in_out == 'INPUT']
|
||||
assert [item.name for item in inputs] == ['Geometry', 'Symmetry Direction', 'Depth Offset', 'Fill Sides', 'Fill Smooth']
|
||||
assert [item.name for item in inputs] == [
|
||||
'Geometry', 'Direction', 'Offset', 'Scale', 'Fill Sides', 'Fill Smooth',
|
||||
]
|
||||
assert inputs[3].default_value == 1
|
||||
assert (inputs[2].min_value, inputs[2].max_value) == (-10, 10)
|
||||
assert (inputs[3].min_value, inputs[3].max_value) == (0, 2)
|
||||
assert inputs[-1].default_value == 2 and inputs[-2].default_value is True
|
||||
assert (inputs[-1].min_value, inputs[-1].max_value) == (0, 16)
|
||||
assert inputs[-1].parent == inputs[-2].parent
|
||||
assert inputs[-1].parent.name == 'Options'
|
||||
assert inputs[-1].parent.default_closed is True
|
||||
thickness = [item for item in obj.modifiers[0].node_group.interface.items_tree
|
||||
if item.item_type == 'SOCKET' and item.name == 'Thickness']
|
||||
assert len(thickness) == 2 and all(item.default_value == 0 for item in thickness)
|
||||
|
||||
Reference in New Issue
Block a user