Refactor image mesh processing and background removal functionality

- Updated NODE_GROUP_NAME from "O Refine DA3 Surface" to "O DA3 Surface".
- Introduced `remove_surface_background` function to filter out background faces and compact geometry.
- Enhanced `build_image_mesh` to conditionally remove background faces based on a new property.
- Added `snapform_remove_background` and `snapform_keep_original` properties to operators for better control over background handling.
- Updated UI to reflect new properties and improved resolution scaling logic.
- Refactored tests to cover new background removal functionality and ensure proper geometry handling.
- Adjusted geometry nodes and validation scripts to remove references to background handling.
This commit is contained in:
2026-08-12 16:15:14 +08:00
parent 7cd5681a8b
commit c3a151ad2c
13 changed files with 477 additions and 271 deletions

View File

@@ -214,7 +214,6 @@ class AddonTest(unittest.TestCase):
"snapform_edge_culling",
"snapform_face_reduction",
"snapform_depth_smoothing",
"snapform_remove_background",
"snapform_postprocess_method",
):
with self.subTest(property_name=property_name):
@@ -222,6 +221,18 @@ class AddonTest(unittest.TestCase):
self.assertFalse(annotations["snapform_create_camera"]["default"])
self.assertFalse(annotations["snapform_unshaded"]["default"])
self.assertEqual(annotations["snapform_unshaded"]["name"], "Unshaded")
self.assertFalse(annotations["snapform_remove_background"]["default"])
self.assertFalse(annotations["snapform_keep_original"]["default"])
self.assertEqual(
annotations["snapform_process_res_factor"]["default"],
100.0,
)
self.assertEqual(annotations["snapform_process_res_factor"]["min"], 1.0)
self.assertEqual(annotations["snapform_process_res_factor"]["max"], 100.0)
self.assertEqual(
annotations["snapform_process_res_factor"]["subtype"],
"PERCENTAGE",
)
def test_image_mesh_uses_o_prefixed_attributes_and_bundled_nodes(self):
image_mesh = self.addon.image_mesh
@@ -231,7 +242,7 @@ class AddonTest(unittest.TestCase):
self.assertTrue(all(name.startswith("o_") for name in names))
self.assertTrue(image_mesh.node_asset_path().is_file())
self.assertEqual(image_mesh.NODE_GROUP_NAME, "O Refine DA3 Surface")
self.assertEqual(image_mesh.NODE_GROUP_NAME, "O DA3 Surface")
self.assertEqual(image_mesh.NODE_ASSET_NAME, "O_SnapForm.blend")
def test_depth_plane_uses_bundled_nodes_and_material_attributes(self):
@@ -242,10 +253,6 @@ class AddonTest(unittest.TestCase):
image_depth.DEPTH_PLANE_NODE_GROUP_NAME,
"O DA3 Depth Plane",
)
self.assertEqual(
image_depth.REMOVE_BACKGROUND_ATTRIBUTE,
"o_remove_background",
)
self.assertEqual(
image_depth.MATERIAL_DISPLACEMENT_ATTRIBUTE,
"o_material_displacement",
@@ -279,6 +286,33 @@ class AddonTest(unittest.TestCase):
float(np.median(edge_lengths) * np.sqrt(10.0) * 2.0),
)
def test_surface_background_filter_compacts_geometry_and_attributes(self):
vertices = np.arange(18, dtype=np.float32).reshape(6, 3)
faces = np.asarray(((0, 1, 2), (2, 3, 4)), dtype=np.int32)
uv = np.arange(12, dtype=np.float32).reshape(6, 2)
attributes = {
key: np.arange(6 if name in self.addon.image_mesh.POINT_ATTRIBUTES else 2)
for name, (_data_type, key) in {
**self.addon.image_mesh.POINT_ATTRIBUTES,
**self.addon.image_mesh.FACE_ATTRIBUTES,
}.items()
}
attributes["face_background"] = np.asarray((True, False))
result = self.addon.image_mesh.remove_surface_background(
vertices,
faces,
uv,
attributes,
)
filtered_vertices, filtered_faces, filtered_uv, filtered_attributes = result
np.testing.assert_array_equal(filtered_vertices, vertices[[2, 3, 4]])
np.testing.assert_array_equal(filtered_faces, ((0, 1, 2),))
np.testing.assert_array_equal(filtered_uv, uv[[2, 3, 4]])
self.assertEqual(len(filtered_attributes["point_depth"]), 3)
self.assertEqual(len(filtered_attributes["face_background"]), 1)
def test_registers_image_background_operators(self):
self.assertIn(self.addon.RemoveImageBackground, self.addon.CLASSES)
self.assertIn(self.addon.ApplyRemovedBackground, self.addon.CLASSES)
@@ -481,7 +515,8 @@ class AddonTest(unittest.TestCase):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ORIGINAL"
operator.snapform_unshaded = False
operator.snapform_delete_original = False
operator.snapform_remove_background = True
operator.snapform_keep_original = True
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(name="img.png"),
@@ -513,7 +548,8 @@ class AddonTest(unittest.TestCase):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ORIGINAL"
operator.snapform_unshaded = False
operator.snapform_delete_original = False
operator.snapform_remove_background = True
operator.snapform_keep_original = True
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(
@@ -552,6 +588,39 @@ class AddonTest(unittest.TestCase):
self.assertFalse(calls[0]["snapform_delete_input"])
self.assertFalse(calls[0]["snapform_delete_source"])
def test_original_depth_without_background_uses_frame_extraction(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ORIGINAL"
operator.snapform_unshaded = False
operator.snapform_remove_background = False
operator.snapform_keep_original = False
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(name="img.png"),
)
context = SimpleNamespace(object=source_object, scene=object())
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
run_job=lambda **options: calls.append(options)
or {"RUNNING_MODAL"},
),
)
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
with patch.object(
self.addon.image_depth,
"prepare_image_input",
return_value=(input_path, False),
):
result = operator.execute(context)
self.assertEqual(result, {"FINISHED"})
self.assertEqual(calls[0]["job_type"], "ORIGINAL_FRAMES")
self.assertTrue(calls[0]["snapform_delete_source"])
def test_conversion_dialog_exposes_requested_settings(self):
operator = self.addon.ConvertImageToDepthPlane
@@ -562,10 +631,21 @@ class AddonTest(unittest.TestCase):
self.assertTrue(operator.__annotations__["snapform_use_ray_pose"]["default"])
self.assertEqual(
operator.__annotations__["snapform_process_res_factor"]["default"],
100.0,
)
self.assertEqual(
operator.__annotations__["snapform_process_res_factor"]["min"],
1.0,
)
self.assertTrue(
operator.__annotations__["snapform_delete_original"]["default"]
self.assertEqual(
operator.__annotations__["snapform_process_res_factor"]["max"],
100.0,
)
self.assertFalse(
operator.__annotations__["snapform_remove_background"]["default"]
)
self.assertFalse(
operator.__annotations__["snapform_keep_original"]["default"]
)
self.assertFalse(
operator.__annotations__["snapform_unshaded"]["default"]
@@ -578,7 +658,8 @@ class AddonTest(unittest.TestCase):
"snapform_model",
"snapform_use_ray_pose",
"snapform_process_res_factor",
"snapform_delete_original",
"snapform_remove_background",
"snapform_keep_original",
},
)
depth_sources = operator.__annotations__["snapform_depth_source"]
@@ -690,9 +771,10 @@ class AddonTest(unittest.TestCase):
operator.snapform_depth_source = "ESTIMATE"
operator.snapform_model = "BASE"
operator.snapform_use_ray_pose = True
operator.snapform_process_res_factor = 1.0
operator.snapform_process_res_factor = 50.0
operator.snapform_unshaded = False
operator.snapform_delete_original = False
operator.snapform_remove_background = True
operator.snapform_keep_original = True
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
@@ -817,9 +899,10 @@ class AddonTest(unittest.TestCase):
operator.snapform_depth_source = "ESTIMATE"
operator.snapform_model = "GIANT_1_1"
operator.snapform_use_ray_pose = True
operator.snapform_process_res_factor = 0.5
operator.snapform_process_res_factor = 50.0
operator.snapform_unshaded = True
operator.snapform_delete_original = True
operator.snapform_remove_background = True
operator.snapform_keep_original = False
image = SimpleNamespace(
name="img.png",
filepath="",
@@ -855,7 +938,7 @@ class AddonTest(unittest.TestCase):
self.assertEqual(result, {"FINISHED"})
save_packed_image.assert_called_once_with(image, context.scene)
self.assertEqual(calls[0]["snapform_input_path"], str(packed_path))
self.assertEqual(calls[0]["snapform_process_res"], 960)
self.assertEqual(calls[0]["snapform_process_res"], 966)
self.assertEqual(calls[0]["snapform_model_key"], "GIANT_1_1")
self.assertTrue(calls[0]["snapform_remove_background"])
self.assertTrue(calls[0]["snapform_unshaded"])