Refactor Cutout Tool to Use World Space Edge Length
- Replaced the Mesh Detail parameter with a new Edge Length property to define cutout sampling in physical units. - Introduced Minimum Relative Edge Length preference to control effective spacing based on the bounding box of the cutout content. - Updated tests to validate the new edge length functionality and ensure consistent behavior across different resolutions and canvas sizes. - Removed deprecated parameters and adjusted related properties to align with the new edge length implementation. - Enhanced geometry sampling logic to utilize effective world spacing for both standard and fine outline modes.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
from ...preferences import addon_preferences
|
||||
from ...preferences import addon_preferences, configured_min_cutout_relative_edge_length
|
||||
from ...common.image import image_empty_bounds
|
||||
from . import balloon
|
||||
|
||||
@@ -824,11 +824,9 @@ def crop_plane_bounds(source_object, crop_bounds, source_size):
|
||||
|
||||
def extract_cutout_contours(
|
||||
content_values,
|
||||
point_spacing,
|
||||
sampling_resolution,
|
||||
threshold=CUTOUT_ALPHA_THRESHOLD,
|
||||
):
|
||||
height, width = content_values.shape
|
||||
sampling_resolution = max(width, height) / float(point_spacing)
|
||||
contour_resolution = max(512, min(round(sampling_resolution * 8), 1024))
|
||||
return extract_selection_contours(
|
||||
content_values,
|
||||
@@ -837,28 +835,28 @@ def extract_cutout_contours(
|
||||
)
|
||||
|
||||
|
||||
def limit_cutout_point_spacing(point_spacing, crop_size, max_mesh_resolution):
|
||||
crop_width, crop_height = (int(value) for value in crop_size)
|
||||
spacing = float(point_spacing)
|
||||
if crop_width <= 0 or crop_height <= 0:
|
||||
raise ValueError("The Cutout bounds must be positive")
|
||||
if spacing <= 0.0:
|
||||
raise ValueError("Cutout point spacing must be greater than zero")
|
||||
maximum = int(max_mesh_resolution)
|
||||
if maximum <= 0:
|
||||
raise ValueError("Maximum Cutout mesh resolution must be positive")
|
||||
return max(spacing, max(crop_width, crop_height) / maximum)
|
||||
def effective_cutout_edge_length(
|
||||
context, source_object, edge_length, content_values, content_bounds,
|
||||
threshold=CUTOUT_ALPHA_THRESHOLD,
|
||||
):
|
||||
"""Resolve target spacing in Blender world units from visible content bounds."""
|
||||
import numpy as np
|
||||
|
||||
|
||||
def cutout_sampling_spacing(source_object, point_spacing):
|
||||
import math
|
||||
|
||||
x_min, x_max, y_min, y_max = image_empty_bounds(source_object)
|
||||
metric, _world_z_scale = world_plane_metric(source_object.matrix_world)
|
||||
width = (x_max - x_min) * math.sqrt(max(metric[0][0], 0.0))
|
||||
height = (y_max - y_min) * math.sqrt(max(metric[1][1], 0.0))
|
||||
image_width, image_height = (int(value) for value in source_object.data.size)
|
||||
return max(width / image_width, height / image_height) * float(point_spacing)
|
||||
if not math.isfinite(edge_length) or edge_length <= 0:
|
||||
raise ValueError("Cutout edge length must be greater than zero")
|
||||
values = np.asarray(content_values)
|
||||
visible = (values > 0) & (values >= threshold)
|
||||
rows = np.flatnonzero(visible.any(axis=1))
|
||||
columns = np.flatnonzero(visible.any(axis=0))
|
||||
if not len(rows):
|
||||
raise ValueError("The Cutout selection contains no visible image pixels")
|
||||
bounds = crop_plane_bounds(source_object, content_bounds, tuple(source_object.data.size))
|
||||
metric, _ = world_plane_metric(source_object.matrix_world)
|
||||
metric_transform(metric)
|
||||
width = (bounds[1] - bounds[0]) * (columns.max() + 1 - columns.min()) / values.shape[1]
|
||||
height = (bounds[3] - bounds[2]) * (rows.max() + 1 - rows.min()) / values.shape[0]
|
||||
longest = max(width * math.sqrt(metric[0][0]), height * math.sqrt(metric[1][1]))
|
||||
return max(float(edge_length), longest * configured_min_cutout_relative_edge_length(context))
|
||||
|
||||
|
||||
def selection_calibration(content_values, target_shape, threshold=CUTOUT_ALPHA_THRESHOLD):
|
||||
@@ -885,7 +883,7 @@ def selection_calibration(content_values, target_shape, threshold=CUTOUT_ALPHA_T
|
||||
def build_base_shape(
|
||||
context,
|
||||
source_object,
|
||||
point_spacing,
|
||||
edge_length,
|
||||
content_values,
|
||||
content_bounds,
|
||||
*,
|
||||
@@ -899,31 +897,38 @@ def build_base_shape(
|
||||
content_bounds,
|
||||
tuple(source_object.data.size),
|
||||
)
|
||||
world_metric, world_z_scale = world_plane_metric(source_object.matrix_world)
|
||||
if world_z_scale <= 1e-10:
|
||||
raise ValueError("The Image Empty has a degenerate world transform")
|
||||
pixel_transform = np.diag((
|
||||
(bounds[1] - bounds[0]) / content_values.shape[1],
|
||||
-(bounds[3] - bounds[2]) / content_values.shape[0],
|
||||
)) @ metric_transform(world_metric)
|
||||
longest = max(
|
||||
np.linalg.norm(pixel_transform[0]) * content_values.shape[1],
|
||||
np.linalg.norm(pixel_transform[1]) * content_values.shape[0],
|
||||
)
|
||||
fine_mesh = None
|
||||
selection_contours = None
|
||||
if fine_outline:
|
||||
from .fine_mesh import build_fine_mesh
|
||||
|
||||
fine_mesh = (
|
||||
*build_fine_mesh(content_values, point_spacing, alpha_threshold),
|
||||
*build_fine_mesh(content_values, edge_length, alpha_threshold, pixel_transform=pixel_transform),
|
||||
(content_values.shape[1], content_values.shape[0]),
|
||||
)
|
||||
else:
|
||||
selection_contours = extract_cutout_contours(
|
||||
content_values,
|
||||
point_spacing,
|
||||
longest / edge_length,
|
||||
threshold=alpha_threshold,
|
||||
)
|
||||
world_metric, world_z_scale = world_plane_metric(source_object.matrix_world)
|
||||
if world_z_scale <= 1e-10:
|
||||
raise ValueError("The Image Empty has a degenerate world transform")
|
||||
preferences = addon_preferences(context)
|
||||
balloon_algorithm = getattr(preferences, "balloon_profile_algorithm", "POISSON")
|
||||
point_spacing = cutout_sampling_spacing(source_object, point_spacing)
|
||||
domain = cutout_domain(
|
||||
selection_contours,
|
||||
bounds,
|
||||
point_spacing,
|
||||
edge_length,
|
||||
world_metric,
|
||||
balloon_algorithm,
|
||||
fine_mesh=fine_mesh,
|
||||
|
||||
Reference in New Issue
Block a user