feat: Implement weight cutout boundary smoothing for image depth cutout
- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements. - Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values. - Updated specifications to define the behavior of boundary-specific smoothing strength and default settings. - Created tasks to verify the implementation of boundary behavior and asset updates. - Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance. - Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials. - Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
This commit is contained in:
@@ -28,7 +28,7 @@ def test_base_plane_fit_is_stable_for_a_slender_diagonal_domain():
|
||||
assert np.corrcoef(selected_plane, along)[0, 1] > 0.99
|
||||
|
||||
|
||||
def test_depth_balloon_plane_calibration_matches_cutout_axes():
|
||||
def test_depth_symmetry_plane_calibration_matches_cutout_axes():
|
||||
vertices = []
|
||||
uv = []
|
||||
camera_depth = np.empty((3, 3), dtype=np.float32)
|
||||
@@ -40,7 +40,7 @@ def test_depth_balloon_plane_calibration_matches_cutout_axes():
|
||||
uv.append((column * 0.5, 1.0 - row * 0.5))
|
||||
camera_depth[row, column] = 2.0 + 0.2 * local_x + 0.1 * local_y
|
||||
|
||||
reference, direction = depth_calibration.depth_balloon_projection_parameters(
|
||||
reference, direction = depth_calibration.depth_symmetry_projection_parameters(
|
||||
vertices,
|
||||
uv,
|
||||
camera_depth,
|
||||
@@ -58,7 +58,7 @@ def test_depth_balloon_plane_calibration_matches_cutout_axes():
|
||||
assert np.allclose(direction, expected_direction, atol=1e-6)
|
||||
|
||||
|
||||
def test_depth_balloon_reference_uses_the_shared_upper_depth_percentile():
|
||||
def test_depth_symmetry_reference_uses_the_shared_upper_depth_percentile():
|
||||
size = 10
|
||||
row, column = np.indices((size, size))
|
||||
camera_depth = np.full((size, size), 2.0, dtype=np.float32)
|
||||
@@ -70,7 +70,7 @@ def test_depth_balloon_reference_uses_the_shared_upper_depth_percentile():
|
||||
vertices.append((float(x), float(size - 1 - y), 0.0))
|
||||
uv.append((x / (size - 1), 1.0 - y / (size - 1)))
|
||||
|
||||
reference, direction = depth_calibration.depth_balloon_projection_parameters(
|
||||
reference, direction = depth_calibration.depth_symmetry_projection_parameters(
|
||||
vertices,
|
||||
uv,
|
||||
camera_depth,
|
||||
|
||||
@@ -10,7 +10,7 @@ from anyimage.operators.cutout_tool.balloon import poisson_balloon_profile
|
||||
@pytest.mark.parametrize("enabled", [False, True])
|
||||
def test_depth_presets_always_generate_the_correct_normal_space(enabled):
|
||||
assert normal_mode_for_shape("DEPTH_SOLID", enabled) == "OBJECT"
|
||||
assert normal_mode_for_shape("DEPTH_BALLOON", enabled) == "TANGENT"
|
||||
assert normal_mode_for_shape("DEPTH_SYMMETRY", enabled) == "TANGENT"
|
||||
for shape in ("FLAT", "SOLID"):
|
||||
assert normal_mode_for_shape(shape, enabled) == ("TANGENT" if enabled else "NONE")
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ def test_cutout_pie_passes_image_lasso_options_to_all_ai_shapes(monkeypatch):
|
||||
assert [call[2].shape for call in window_manager.pie.calls] == [
|
||||
"FLAT",
|
||||
"SOLID",
|
||||
"DEPTH_BALLOON",
|
||||
"DEPTH_SYMMETRY",
|
||||
"DEPTH_SOLID",
|
||||
]
|
||||
assert all(call[2].generate_normal for call in window_manager.pie.calls)
|
||||
@@ -155,10 +155,10 @@ def test_cutout_pie_passes_image_lasso_options_to_all_ai_shapes(monkeypatch):
|
||||
|
||||
def test_cutout_normal_space_is_derived_from_shape():
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_SOLID", False) == "OBJECT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_BALLOON", False) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_SYMMETRY", False) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("FLAT", True) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("SOLID", True) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_BALLOON", True) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_SYMMETRY", True) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_SOLID", True) == "OBJECT"
|
||||
|
||||
|
||||
|
||||
@@ -175,22 +175,10 @@ def test_balloon_build_starts_from_the_unified_single_sheet():
|
||||
profile_attribute.data.foreach_get("value", profile)
|
||||
assert profile.min() == 0.0 and profile.max() > 0.0
|
||||
material_tree = result.data.materials[0].node_tree
|
||||
normal_scale_attribute = next(
|
||||
node
|
||||
for node in material_tree.nodes
|
||||
if node.bl_idname == "ShaderNodeAttribute"
|
||||
)
|
||||
assert normal_scale_attribute.attribute_type == "GEOMETRY"
|
||||
assert normal_scale_attribute.attribute_name == cutout_shape.NORMAL_SCALE_ATTRIBUTE_NAME
|
||||
assert not any(node.bl_idname == "ShaderNodeAttribute" for node in material_tree.nodes)
|
||||
image_layer = _group_node(material_data.material_node_group(), material_tree)
|
||||
normal_scale_link = next(
|
||||
link
|
||||
for link in material_tree.links
|
||||
if link.to_node == image_layer
|
||||
and link.to_socket == image_layer.inputs["Normal Scale"]
|
||||
)
|
||||
assert normal_scale_link.from_node == normal_scale_attribute
|
||||
assert normal_scale_link.from_socket == normal_scale_attribute.outputs["Fac"]
|
||||
assert not image_layer.inputs["Normal Scale"].is_linked
|
||||
assert image_layer.inputs["Normal Scale"].default_value == 1.0
|
||||
assert result.modifiers[0].node_group.name == cutout_shape.CUTOUT_NODE_GROUP_NAMES["SOLID"]
|
||||
inward = result.matrix_world.to_3x3() @ Vector((0.0, 0.0, -1.0))
|
||||
assert np.allclose(tuple(inward), (0.0, 0.0, -1.0), atol=1e-6)
|
||||
@@ -219,7 +207,7 @@ def test_teddy_preference_writes_the_shared_balloon_attribute():
|
||||
profile = np.empty(len(result.data.vertices), dtype=np.float32)
|
||||
attribute.data.foreach_get("value", profile)
|
||||
assert profile.min() == 0.0 and profile.max() > 0.0
|
||||
assert any(
|
||||
assert not any(
|
||||
node.bl_idname == "ShaderNodeAttribute"
|
||||
for node in result.data.materials[0].node_tree.nodes
|
||||
)
|
||||
@@ -585,10 +573,11 @@ def test_cutout_cleanup_uses_absolute_area_with_many_small_islands():
|
||||
assert len(_evaluated_positions(obj)) == 9
|
||||
|
||||
|
||||
def test_depth_balloon_uses_metric_plane_inputs():
|
||||
@pytest.mark.parametrize("gesture", ("LASSO", "POLYLINE"))
|
||||
def test_depth_symmetry_uses_metric_plane_inputs(gesture):
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
image = bpy.data.images.new("Depth Balloon", width=100, height=100, alpha=True)
|
||||
source = bpy.data.objects.new("Depth Balloon", None)
|
||||
image = bpy.data.images.new("Depth Symmetry", width=100, height=100, alpha=True)
|
||||
source = bpy.data.objects.new("Depth Symmetry", None)
|
||||
source.empty_display_type = "IMAGE"
|
||||
source.data = image
|
||||
source.empty_display_size = 2.0
|
||||
@@ -619,12 +608,13 @@ def test_depth_balloon_uses_metric_plane_inputs():
|
||||
selection_values[:, :2] = 0.0
|
||||
result = _create_cutout_shape(
|
||||
source,
|
||||
"DEPTH_BALLOON",
|
||||
"DEPTH_SYMMETRY",
|
||||
0.1,
|
||||
depth_image=depth_image,
|
||||
depth_metadata=depth_metadata,
|
||||
bounds=crop_bounds,
|
||||
selection_values=selection_values,
|
||||
gesture=gesture,
|
||||
)
|
||||
|
||||
assert all(abs(vertex.co.z) < 1e-8 for vertex in result.data.vertices)
|
||||
@@ -634,6 +624,9 @@ def test_depth_balloon_uses_metric_plane_inputs():
|
||||
result.name,
|
||||
}
|
||||
modifier = result.modifiers[0]
|
||||
mode_id = object_data.modifier_input_identifier(modifier.node_group, "Mode")
|
||||
assert modifier_value(modifier, mode_id) == 1
|
||||
object_data.set_modifier_input(modifier, mode_id, 0)
|
||||
depth_image_id = object_data.modifier_input_identifier(
|
||||
modifier.node_group,
|
||||
"Depth Image",
|
||||
|
||||
Reference in New Issue
Block a user