Refactor Cutout and Image Editing Logic
- Renamed "Base Plane Depth" to "Reference Depth" across validate_cutout.py for consistency. - Introduced a new specification for Cutout mesh sampling to ensure source pixel spacing is preserved and limits are applied correctly. - Unified naming conventions for image editing results to maintain consistency across Remove Background, Upscale, and Crop operations. - Updated the handling of Refine Selection to ensure it retains the original bounds and does not apply unnecessary cropping. - Enhanced material generation logic to connect image color directly to IOR Level, improving the rendering of dark areas. - Established clear requirements for geometry depth controls and image result materials to ensure consistent behavior across different components. - Removed outdated logic and ensured all changes are documented and tested for regression.
This commit is contained in:
@@ -157,6 +157,51 @@ def test_keep_original_places_and_activates_result_copy():
|
||||
assert bpy.context.view_layer.objects.active == placed
|
||||
|
||||
|
||||
def test_image_edit_result_uses_complete_shared_source_name():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
source_image = bpy.data.images.new("Poster.png", width=2, height=2, alpha=True)
|
||||
source = bpy.data.objects.new("Poster", None)
|
||||
source.empty_display_type = "IMAGE"
|
||||
source.data = source_image
|
||||
shared = bpy.data.objects.new("Poster Shared", None)
|
||||
shared.empty_display_type = "IMAGE"
|
||||
shared.data = source_image
|
||||
bpy.context.collection.objects.link(source)
|
||||
bpy.context.collection.objects.link(shared)
|
||||
|
||||
result_image = image_data.create_image_edit_result(
|
||||
source_image,
|
||||
np.zeros(4 * 4 * 4, dtype=np.float32),
|
||||
(4, 4),
|
||||
)
|
||||
image_data.replace_empty_image(source, result_image)
|
||||
|
||||
assert shared.data == source_image
|
||||
assert source.data == result_image
|
||||
assert result_image.name == "Poster.png.001"
|
||||
assert "_color" not in result_image.name
|
||||
|
||||
|
||||
def test_image_edit_result_recovers_unshared_source_name():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
source_image = bpy.data.images.new("Poster.png", width=2, height=2, alpha=True)
|
||||
source = bpy.data.objects.new("Poster", None)
|
||||
source.empty_display_type = "IMAGE"
|
||||
source.data = source_image
|
||||
bpy.context.collection.objects.link(source)
|
||||
|
||||
result_image = image_data.create_image_edit_result(
|
||||
source_image,
|
||||
np.zeros(4 * 4 * 4, dtype=np.float32),
|
||||
(4, 4),
|
||||
)
|
||||
image_data.replace_empty_image(source, result_image)
|
||||
|
||||
assert source.data == result_image
|
||||
assert result_image.name == "Poster.png"
|
||||
assert bpy.data.images.get("Poster.png") == result_image
|
||||
|
||||
|
||||
def test_object_normal_image_follows_cutout_inward_axis():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
image = bpy.data.images.new(
|
||||
@@ -292,7 +337,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
"Subdivide",
|
||||
"Thickness",
|
||||
"Depth Scale",
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
"Uniform Scale",
|
||||
"Depth Image",
|
||||
]
|
||||
@@ -306,7 +351,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
"Smooth Iterations",
|
||||
"Cleanup",
|
||||
"Smooth",
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
"Depth Direction",
|
||||
"Uniform Scale",
|
||||
"Depth Image",
|
||||
@@ -316,7 +361,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
assert [item.name for item in panels[0].interface_items] == [
|
||||
"Cleanup",
|
||||
"Smooth",
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
"Depth Direction",
|
||||
]
|
||||
assert [item.name for item in panels[1].interface_items] == [
|
||||
@@ -375,7 +420,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
"Thickness",
|
||||
"Depth Scale",
|
||||
"Double Sided",
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
"Depth Direction",
|
||||
"Uniform Scale",
|
||||
"Depth Image",
|
||||
@@ -384,7 +429,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
"Geometry",
|
||||
"Thickness",
|
||||
"Depth Scale",
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
"Uniform Scale",
|
||||
"Depth Image",
|
||||
]
|
||||
@@ -405,7 +450,7 @@ def test_cutout_modifier_wraps_four_menu_controlled_shape_groups():
|
||||
"Thickness": set(cutout_shape.CUTOUT_SHAPES),
|
||||
"Depth Scale": {"DEPTH_BALLOON", "DEPTH_SURFACE"},
|
||||
"Double Sided": {"DEPTH_BALLOON"},
|
||||
"Base Plane Depth": {"DEPTH_BALLOON", "DEPTH_SURFACE"},
|
||||
"Reference Depth": {"DEPTH_BALLOON", "DEPTH_SURFACE"},
|
||||
"Depth Direction": {"DEPTH_BALLOON"},
|
||||
"Uniform Scale": {"DEPTH_BALLOON", "DEPTH_SURFACE"},
|
||||
"Depth Image": {"DEPTH_BALLOON", "DEPTH_SURFACE"},
|
||||
@@ -740,6 +785,50 @@ def test_cutout_material_uses_ior_1_2():
|
||||
if node.bl_idname == "ShaderNodeBsdfPrincipled"
|
||||
)
|
||||
assert np.isclose(shader.inputs["IOR"].default_value, 1.2)
|
||||
image_layer = _group_node(
|
||||
material_data.material_node_group(),
|
||||
result.data.materials[0].node_tree,
|
||||
)
|
||||
ior_level_link = next(
|
||||
link
|
||||
for link in result.data.materials[0].node_tree.links
|
||||
if link.to_node == shader
|
||||
and link.to_socket == shader.inputs["Specular IOR Level"]
|
||||
)
|
||||
assert ior_level_link.from_node == image_layer
|
||||
assert ior_level_link.from_socket == image_layer.outputs["Color"]
|
||||
|
||||
|
||||
def test_plane_material_color_drives_ior_level_but_shadeless_does_not():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
image = bpy.data.images.new("Plane", width=2, height=2, alpha=True)
|
||||
source = SimpleNamespace(data=SimpleNamespace(name="Plane"))
|
||||
|
||||
material = material_data.create_image_material(source, image, 0.0)
|
||||
shader = next(
|
||||
node
|
||||
for node in material.node_tree.nodes
|
||||
if node.bl_idname == "ShaderNodeBsdfPrincipled"
|
||||
)
|
||||
image_layer = _group_node(material_data.material_node_group(), material.node_tree)
|
||||
assert any(
|
||||
link.from_node == image_layer
|
||||
and link.from_socket == image_layer.outputs["Color"]
|
||||
and link.to_node == shader
|
||||
and link.to_socket == shader.inputs["Specular IOR Level"]
|
||||
for link in material.node_tree.links
|
||||
)
|
||||
|
||||
shadeless = material_data.create_image_material(
|
||||
source,
|
||||
image,
|
||||
0.0,
|
||||
shadeless=True,
|
||||
)
|
||||
assert not any(
|
||||
node.bl_idname == "ShaderNodeBsdfPrincipled"
|
||||
for node in shadeless.node_tree.nodes
|
||||
)
|
||||
|
||||
def test_cutout_material_uses_flat_tangent_normal():
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
@@ -820,7 +909,7 @@ def test_depth_surface_protected_smooth_evaluates():
|
||||
)
|
||||
reference_id = object_data.modifier_input_identifier(
|
||||
modifier.node_group,
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
)
|
||||
assert modifier[reference_id] == 8.0
|
||||
object_data.set_modifier_input(
|
||||
@@ -1013,7 +1102,7 @@ def test_converted_planes_center_the_origin_without_moving_world_corners():
|
||||
depth_modifier[
|
||||
object_data.modifier_input_identifier(
|
||||
depth_modifier.node_group,
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
)
|
||||
],
|
||||
uniform_scale,
|
||||
@@ -1078,7 +1167,7 @@ def test_depth_plane_builds_a_fixed_base_terrain_block():
|
||||
evaluated.to_mesh_clear()
|
||||
|
||||
set_input("Depth Scale", 1.0)
|
||||
set_input("Base Plane Depth", 1.2)
|
||||
set_input("Reference Depth", 1.2)
|
||||
outward = _evaluated_positions(result)
|
||||
assert np.isclose(outward[:, 2].min(), 0.0, atol=1e-6)
|
||||
assert np.isclose(outward[:, 2].max(), 0.3, atol=1e-5)
|
||||
@@ -1092,23 +1181,23 @@ def test_depth_plane_builds_a_fixed_base_terrain_block():
|
||||
assert len(z_levels) >= 4
|
||||
assert np.allclose(np.diff(z_levels), np.diff(z_levels)[0], atol=1e-5)
|
||||
|
||||
set_input("Base Plane Depth", 0.95)
|
||||
set_input("Reference Depth", 0.95)
|
||||
inward = _evaluated_positions(result)
|
||||
assert np.isclose(inward[:, 2].min(), 0.0, atol=1e-6)
|
||||
assert np.isclose(inward[:, 2].max(), 0.05, atol=1e-5)
|
||||
|
||||
set_input("Base Plane Depth", 0.0)
|
||||
set_input("Reference Depth", 0.0)
|
||||
limited = _evaluated_positions(result)
|
||||
assert np.isclose(limited[:, 2].min(), 0.0, atol=1e-6)
|
||||
assert limited[:, 2].max() > 0.0
|
||||
assert limited[:, 2].max() < 0.001
|
||||
|
||||
set_input("Base Plane Depth", 1.3)
|
||||
set_input("Reference Depth", 1.3)
|
||||
outward_beyond_thickness = _evaluated_positions(result)
|
||||
assert np.isclose(outward_beyond_thickness[:, 2].max(), 0.4, atol=1e-5)
|
||||
|
||||
set_input("Thickness", 0.0)
|
||||
set_input("Base Plane Depth", 1.2)
|
||||
set_input("Reference Depth", 1.2)
|
||||
sheet = _evaluated_positions(result)
|
||||
assert np.allclose(sheet[:, 2], 0.2, atol=1e-5)
|
||||
|
||||
@@ -1401,7 +1490,7 @@ def test_depth_balloon_uses_metric_plane_inputs_without_controller():
|
||||
|
||||
depth_reference_id = object_data.modifier_input_identifier(
|
||||
modifier.node_group,
|
||||
"Base Plane Depth",
|
||||
"Reference Depth",
|
||||
)
|
||||
assert not np.isclose(modifier[depth_reference_id], 8.0)
|
||||
depth_direction_id = object_data.modifier_input_identifier(
|
||||
|
||||
Reference in New Issue
Block a user