feat(selection): separate selection geometry from image content processing

- Introduced a new design for selection rasterization that decouples the selection geometry from the image content, ensuring that the SelectionMask is independent of the source image's alpha and color.
- Updated the `rasterize_selection_path` function to only accept selection paths and image dimensions, removing parameters related to source alpha and alpha thresholds.
- Implemented a new approach for handling selection paths using non-zero winding fill, ensuring overlapping areas are preserved.
- Enhanced performance by utilizing a scanline algorithm for rasterization, significantly reducing processing time for high-resolution images.
- Updated internal documentation and added comprehensive tests to validate the new behavior and performance improvements.
- Ensured backward compatibility by maintaining existing interfaces where necessary while introducing breaking changes in the selection processing logic.
This commit is contained in:
2026-09-01 22:36:20 +08:00
parent 2ff7b04f56
commit 7f6e47432e
54 changed files with 2831 additions and 634 deletions

View File

@@ -50,7 +50,7 @@ class GenerateDepthPlane(JobOperatorBase, bpy.types.Operator):
input_path: bpy.props.StringProperty(options=_HIDDEN)
source_object_name: bpy.props.StringProperty(options=_HIDDEN)
mesh_detail: bpy.props.IntProperty(default=0, min=0, max=10, options=_HIDDEN)
mesh_detail: bpy.props.IntProperty(default=6, min=0, max=10, options=_HIDDEN)
model: bpy.props.StringProperty(default=DEFAULT_MOGE2_MODEL_KEY, options=_HIDDEN)
resolution_level: bpy.props.IntProperty(default=5, min=0, max=9, options=_HIDDEN)
max_input_size: bpy.props.IntProperty(
@@ -160,7 +160,7 @@ class ConvertToDepthPlane(bpy.types.Operator):
bl_description = "Convert the complete Image Empty into a front-projected relief"
bl_options = {"REGISTER", "UNDO"}
mesh_detail: bpy.props.IntProperty(name="Subdivide", default=0, min=0, max=10)
mesh_detail: bpy.props.IntProperty(name="Subdivide", default=6, min=0, max=10)
@classmethod
def poll(cls, context):

View File

@@ -9,18 +9,29 @@ from ...common.node import load_node_group
MESH_PLANE_NODE_GROUP_NAME = "O Mesh Plane"
def build_mesh_plane_matrix(matrix_world, bounds):
from mathutils import Matrix
x_min, x_max, y_min, y_max = bounds
center = ((x_min + x_max) * 0.5, (y_min + y_max) * 0.5, 0.0)
return matrix_world @ Matrix.Translation(center)
def create_mesh_plane_mesh(name, bounds):
x_min, x_max, y_min, y_max = bounds
if x_max <= x_min or y_max <= y_min:
raise ValueError("The plane has invalid dimensions")
half_width = (x_max - x_min) * 0.5
half_height = (y_max - y_min) * 0.5
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(
(
(x_min, y_min, 0.0),
(x_max, y_min, 0.0),
(x_max, y_max, 0.0),
(x_min, y_max, 0.0),
(-half_width, -half_height, 0.0),
(half_width, -half_height, 0.0),
(half_width, half_height, 0.0),
(-half_width, half_height, 0.0),
),
(),
((0, 1, 2, 3),),
@@ -37,7 +48,6 @@ def create_mesh_plane_mesh(name, bounds):
def add_mesh_plane_processing(
plane_object,
material,
subdivisions=0,
thickness=0.0,
):
@@ -52,7 +62,6 @@ def add_mesh_plane_processing(
for name, value in (
("Subdivide", max(0, int(subdivisions))),
("Thickness", max(0.0, float(thickness))),
("Material", material),
):
identifier = modifier_input_identifier(node_group, name)
set_modifier_input(modifier, identifier, value)
@@ -72,11 +81,13 @@ def create_mesh_plane_object(
mesh.materials.append(material)
plane = bpy.data.objects.new(name, mesh)
context.collection.objects.link(plane)
if matrix_world is not None:
plane.matrix_world = matrix_world.copy()
if matrix_world is None:
from mathutils import Matrix
matrix_world = Matrix.Identity(4)
plane.matrix_world = build_mesh_plane_matrix(matrix_world, bounds)
add_mesh_plane_processing(
plane,
material,
subdivisions=subdivisions,
thickness=thickness,
)

View File

@@ -7,6 +7,7 @@ from ...common.image import (
image_empty_bounds,
image_pixels,
load_color_result_image,
load_normal_result_image,
require_image_empty,
)
from ...common.depth import (
@@ -21,7 +22,11 @@ from ...common.object import (
set_modifier_input,
)
from ...common.node import load_node_group
from .mesh_plane import create_mesh_plane_mesh, create_mesh_plane_object
from .mesh_plane import (
build_mesh_plane_matrix,
create_mesh_plane_mesh,
create_mesh_plane_object,
)
DEPTH_PLANE_NODE_GROUP_NAME = "O Image Depth Plane"
@@ -65,26 +70,27 @@ def create_depth_plane_object(
depth_metadata,
):
source_name = source_object.name
mesh = create_mesh_plane_mesh(source_name, image_empty_bounds(source_object))
bounds = image_empty_bounds(source_object)
mesh = create_mesh_plane_mesh(source_name, bounds)
for polygon in mesh.polygons:
polygon.use_smooth = True
mesh.materials.append(material)
plane = bpy.data.objects.new(source_name, mesh)
context.collection.objects.link(plane)
plane.matrix_world = source_object.matrix_world.copy()
plane.matrix_world = build_mesh_plane_matrix(source_object.matrix_world, bounds)
plane["anyimage_mesh_shape"] = "DEPTH_PLANE"
node_group = load_node_group(DEPTH_PLANE_NODE_GROUP_NAME)
modifier = plane.modifiers.new(name=node_group.name, type="NODES")
modifier.node_group = node_group
modifier.show_group_selector = False
uniform_scale = depth_uniform_scale(source_object, depth_metadata)
for name, value in (
("Subdivide", max(0, min(10, int(subdivisions)))),
("Material", material),
("Thickness", 0.1),
("Depth Amount", 1.0),
("Reference Depth", depth_metadata["reference_depth"]),
("Uniform Scale", depth_uniform_scale(source_object, depth_metadata)),
("Depth Scale", 1.0),
("Base Plane Depth", depth_metadata["reference_depth"] * uniform_scale),
("Uniform Scale", uniform_scale),
("Depth Image", depth_image),
):
set_modifier_input(
@@ -99,19 +105,29 @@ def create_depth_plane_from_result(context, result, operator):
source_object = require_image_empty(operator.source_object_name)
color_image = load_color_result_image(source_object, result.directory)
color_image.name = color_data_name(source_object)
material = create_image_material(
source_object,
color_image,
0.0,
scene=context.scene,
)
depth_metadata = load_depth_metadata(result.file("depth_metadata"))
depth_image = load_depth_result_image(
result.file("z_depth"),
source_object,
depth_metadata,
)
material = None
depth_image = None
normal_image = None
try:
depth_metadata = load_depth_metadata(result.file("depth_metadata"))
depth_image = load_depth_result_image(
result.file("z_depth"),
source_object,
depth_metadata,
)
normal_image = load_normal_result_image(
result.file("tangent_normal"),
source_object,
)
material = create_image_material(
source_object,
color_image,
0.0,
normal_image=normal_image,
normal_space="TANGENT",
ior=1.2,
scene=context.scene,
)
create_depth_plane_object(
context,
source_object,
@@ -121,8 +137,11 @@ def create_depth_plane_from_result(context, result, operator):
depth_metadata,
)
except Exception:
if depth_image.users == 0:
bpy.data.images.remove(depth_image, do_unlink=True)
if material is not None and material.users == 0:
bpy.data.materials.remove(material, do_unlink=True)
for image in (depth_image, normal_image):
if image is not None and image.users == 0:
bpy.data.images.remove(image, do_unlink=True)
raise
if "FINISHED" not in bpy.ops.ed.undo_push(message="Convert to Depth Plane"):
raise RuntimeError("Unable to create the Convert to Depth Plane Undo step")