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

@@ -4,10 +4,12 @@ import bpy
from .image_depth import (
create_material,
draw_process_resolution,
image_depth_model_items,
is_image_empty,
plane_bounds,
prepare_image_input,
process_resolution_scale_property,
remove_temporary_input,
scaled_process_resolution,
video_total_frames,
@@ -33,7 +35,7 @@ FACE_ATTRIBUTES = {
"o_edge_stretch": ("FLOAT", "face_edge_stretch"),
"o_background_face": ("BOOLEAN", "face_background"),
}
NODE_GROUP_NAME = "O Refine DA3 Surface"
NODE_GROUP_NAME = "O DA3 Surface"
NODE_ASSET_NAME = "O_SnapForm.blend"
@@ -202,6 +204,30 @@ def assign_snapform_attributes(mesh, attributes):
)
def remove_surface_background(vertices, faces, uv, attributes):
"""Remove background faces and compact matching point/face data."""
import numpy as np
background_faces = np.asarray(attributes["face_background"], dtype=bool)
kept_faces = faces[~background_faces]
if not len(kept_faces):
raise ValueError("Background removal deleted every surface face")
used_vertices = np.unique(kept_faces)
index_map = np.full(len(vertices), -1, dtype=np.int32)
index_map[used_vertices] = np.arange(len(used_vertices), dtype=np.int32)
filtered_attributes = {}
for _name, (_data_type, key) in POINT_ATTRIBUTES.items():
filtered_attributes[key] = attributes[key][used_vertices]
for _name, (_data_type, key) in FACE_ATTRIBUTES.items():
filtered_attributes[key] = attributes[key][~background_faces]
return (
vertices[used_vertices],
index_map[kept_faces],
uv[used_vertices],
filtered_attributes,
)
def node_asset_path():
return Path(__file__).with_name("assets") / NODE_ASSET_NAME
@@ -333,6 +359,7 @@ def build_image_mesh(
delete_source,
create_camera,
unshaded,
remove_background=False,
):
(
camera_vertices,
@@ -343,6 +370,13 @@ def build_image_mesh(
reference_depth,
attributes,
) = load_image_mesh_data(npz_path)
if remove_background:
camera_vertices, faces, uv, attributes = remove_surface_background(
camera_vertices,
faces,
uv,
attributes,
)
vertices, camera_parameters = empty_anchored_vertices(
source_object,
camera_vertices,
@@ -415,6 +449,10 @@ class BuildImageMesh(bpy.types.Operator):
default=False,
options={"HIDDEN", "SKIP_SAVE"},
)
snapform_remove_background: bpy.props.BoolProperty(
default=False,
options={"HIDDEN", "SKIP_SAVE"},
)
def execute(self, context):
source_object = bpy.data.objects.get(self.snapform_source_object_name)
@@ -429,6 +467,7 @@ class BuildImageMesh(bpy.types.Operator):
self.snapform_delete_source,
self.snapform_create_camera,
self.snapform_unshaded,
self.snapform_remove_background,
)
except (OSError, RuntimeError, ValueError) as error:
self.report({"ERROR"}, str(error))
@@ -452,15 +491,7 @@ class ConvertImageToMesh(bpy.types.Operator):
description="Use ray-based camera pose estimation",
default=True,
)
snapform_process_res_factor: bpy.props.FloatProperty(
name="Resolution Scale",
description="Scale applied to the source image long-edge resolution",
default=1.0,
min=0.1,
max=1.0,
step=10,
precision=2,
)
snapform_process_res_factor: process_resolution_scale_property()
snapform_create_camera: bpy.props.BoolProperty(
name="Create Matching Camera",
description="Create a perspective camera matching the source projection",
@@ -471,15 +502,20 @@ class ConvertImageToMesh(bpy.types.Operator):
description="Display the source color through an Emission shader",
default=False,
)
snapform_delete_original: bpy.props.BoolProperty(
name="Delete Original",
description="Delete the source Image Empty after conversion",
default=True,
snapform_remove_background: bpy.props.BoolProperty(
name="Remove Background",
description="Remove the background during conversion with BEN2",
default=False,
)
snapform_keep_original: bpy.props.BoolProperty(
name="Keep Original",
description="Keep the source Image Empty after conversion",
default=False,
)
@classmethod
def poll(cls, context):
from .models import BEN2_MODEL, MODELS
from .models import MODELS
from .operators import active_job
from .runtime import host_busy, model_ready
@@ -489,7 +525,6 @@ class ConvertImageToMesh(bpy.types.Operator):
and active_job() is None
and not host_busy()
and any(model_ready(model_key) for model_key in MODELS)
and model_ready(BEN2_MODEL.key)
)
def invoke(self, context, _event):
@@ -511,18 +546,12 @@ class ConvertImageToMesh(bpy.types.Operator):
layout = self.layout
layout.prop(self, "snapform_model")
layout.prop(self, "snapform_use_ray_pose")
process_res = scaled_process_resolution(
context.object.data,
self.snapform_process_res_factor,
)
layout.prop(
self,
"snapform_process_res_factor",
text=f"Resolution Scale ({process_res} px)",
)
draw_process_resolution(layout, self, context.object.data)
layout.prop(self, "snapform_create_camera")
layout.prop(self, "snapform_remove_background")
layout.prop(self, "snapform_unshaded")
layout.prop(self, "snapform_delete_original")
layout.separator(type="LINE")
layout.prop(self, "snapform_keep_original")
def execute(self, context):
from .operators import IMAGE_MESH_JOB
@@ -550,10 +579,11 @@ class ConvertImageToMesh(bpy.types.Operator):
snapform_model_key=self.snapform_model,
snapform_use_ray_pose=self.snapform_use_ray_pose,
snapform_process_res=process_res,
snapform_delete_source=self.snapform_delete_original,
snapform_delete_source=not self.snapform_keep_original,
snapform_delete_input=delete_input,
snapform_mesh_create_camera=self.snapform_create_camera,
snapform_unshaded=self.snapform_unshaded,
snapform_remove_background=self.snapform_remove_background,
)
if "CANCELLED" in result:
if delete_input: