feat: Enhance AI integration for Cutout, Image Box, and Image Lasso tools with unified settings and improved shape selection
This commit is contained in:
@@ -161,35 +161,36 @@ def apply_region_settings(operator, context):
|
||||
)
|
||||
|
||||
|
||||
def draw_ai_setup(layout):
|
||||
status = ai_status()
|
||||
if not status["ready"]:
|
||||
layout.operator(
|
||||
"anyimage.setup_ai_environment",
|
||||
text=ai_setup_label(status),
|
||||
icon="IMPORT",
|
||||
)
|
||||
return status
|
||||
|
||||
|
||||
def draw_ai_property(layout, owner, name, status):
|
||||
row = layout.row()
|
||||
row.enabled = status["ready"]
|
||||
row.prop(owner, name)
|
||||
|
||||
|
||||
def draw_region_operator_settings(layout, operator):
|
||||
if operator.gesture not in {"BOX", "LASSO"}:
|
||||
layout.prop(operator, "invert")
|
||||
return
|
||||
status = ai_status()
|
||||
row = layout.row()
|
||||
row.enabled = status["ready"]
|
||||
row.prop(operator, "refine_region")
|
||||
status = draw_ai_setup(layout)
|
||||
draw_ai_property(layout, operator, "refine_region", status)
|
||||
layout.prop(operator, "invert")
|
||||
if not status["ready"]:
|
||||
layout.operator(
|
||||
"anyimage.setup_ai_environment",
|
||||
text=ai_setup_label(status),
|
||||
icon="IMPORT",
|
||||
)
|
||||
|
||||
|
||||
def draw_region_settings(context, layout, setting_name):
|
||||
settings = context.scene.anyimage_settings
|
||||
status = ai_status()
|
||||
row = layout.row()
|
||||
row.enabled = status["ready"]
|
||||
row.prop(settings, setting_name)
|
||||
if not status["ready"]:
|
||||
layout.operator(
|
||||
"anyimage.setup_ai_environment",
|
||||
text=ai_setup_label(status),
|
||||
icon="IMPORT",
|
||||
)
|
||||
status = draw_ai_setup(layout)
|
||||
draw_ai_property(layout, settings, setting_name, status)
|
||||
|
||||
|
||||
def dashed_line_segments(vertices, dash_length=4.0, gap_length=4.0):
|
||||
|
||||
@@ -31,7 +31,11 @@ def set_modifier_input(modifier, identifier, value):
|
||||
inputs = _modifier_input_slots(modifier)
|
||||
if inputs is not None:
|
||||
# Blender 5.2+ exposes inputs as runtime RNA keyed by socket identifier.
|
||||
getattr(inputs, identifier).value = value
|
||||
input_slot = getattr(inputs, identifier)
|
||||
value_property = input_slot.bl_rna.properties["value"]
|
||||
if value_property.type == "ENUM" and type(value) is int:
|
||||
value = value_property.enum_items[value].identifier
|
||||
input_slot.value = value
|
||||
else:
|
||||
# Blender 4.x exposes inputs as modifier ID properties.
|
||||
modifier[identifier] = value
|
||||
|
||||
@@ -75,7 +75,7 @@ def acquire_packed_image(image_data, suffix):
|
||||
with os.fdopen(file_descriptor, "wb") as temporary_file:
|
||||
temporary_file.write(image_data)
|
||||
image = bpy.data.images.load(temporary_path, check_existing=False)
|
||||
image.name = "Clipboard Image"
|
||||
image.name = "Clipboard"
|
||||
image.pack()
|
||||
image[CLIPBOARD_HASH_PROPERTY] = image_hash
|
||||
return image, False
|
||||
|
||||
@@ -4,11 +4,19 @@ from ...preferences import (
|
||||
MAX_CUTOUT_MESH_RESOLUTION,
|
||||
MIN_CUTOUT_MESH_RESOLUTION,
|
||||
)
|
||||
from ...properties import ai_ready
|
||||
from ...common.image_data import image_rgba
|
||||
from ...common.image_region import rasterize_region_path
|
||||
from .geometry import CUTOUT_ALPHA_THRESHOLD
|
||||
from .shape import CUTOUT_SHAPE_LABELS
|
||||
|
||||
CUTOUT_SHAPE_BUTTONS = (
|
||||
("SURFACE", "MESH_GRID"),
|
||||
("BALLOON", "MESH_UVSPHERE"),
|
||||
("DEPTH_BALLOON", "META_BALL"),
|
||||
("DEPTH_SURFACE", "SURFACE_NCURVE"),
|
||||
)
|
||||
|
||||
|
||||
def validate_cutout_region(
|
||||
source_object,
|
||||
@@ -69,55 +77,28 @@ def open_shape_pie(
|
||||
region_path_json = region_path.to_json()
|
||||
settings = context.scene.anyimage_settings
|
||||
point_spacing = settings.cutout_point_spacing
|
||||
generate_normal = bool(getattr(settings, "cutout_generate_normal", False))
|
||||
ready = ai_ready()
|
||||
refine_region = ready and refine_region
|
||||
generate_normal = ready and bool(
|
||||
getattr(settings, "cutout_generate_normal", False)
|
||||
)
|
||||
mesh_resolution = cutout_mesh_resolution(source_object.data.size, point_spacing)
|
||||
|
||||
def draw(menu, _context):
|
||||
pie = menu.layout.menu_pie()
|
||||
_pie_button(
|
||||
pie,
|
||||
"SURFACE",
|
||||
CUTOUT_SHAPE_LABELS["SURFACE"],
|
||||
"MESH_GRID",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_region,
|
||||
generate_normal,
|
||||
mesh_resolution,
|
||||
)
|
||||
_pie_button(
|
||||
pie,
|
||||
"BALLOON",
|
||||
CUTOUT_SHAPE_LABELS["BALLOON"],
|
||||
"MESH_UVSPHERE",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_region,
|
||||
generate_normal,
|
||||
mesh_resolution,
|
||||
)
|
||||
_pie_button(
|
||||
pie,
|
||||
"DEPTH_BALLOON",
|
||||
CUTOUT_SHAPE_LABELS["DEPTH_BALLOON"],
|
||||
"META_BALL",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_region,
|
||||
generate_normal,
|
||||
mesh_resolution,
|
||||
)
|
||||
_pie_button(
|
||||
pie,
|
||||
"DEPTH_SURFACE",
|
||||
CUTOUT_SHAPE_LABELS["DEPTH_SURFACE"],
|
||||
"SURFACE_NCURVE",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_region,
|
||||
generate_normal,
|
||||
mesh_resolution,
|
||||
)
|
||||
buttons = CUTOUT_SHAPE_BUTTONS if ready else CUTOUT_SHAPE_BUTTONS[:2]
|
||||
for shape, icon in buttons:
|
||||
_pie_button(
|
||||
pie,
|
||||
shape,
|
||||
CUTOUT_SHAPE_LABELS[shape],
|
||||
icon,
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_region,
|
||||
generate_normal,
|
||||
mesh_resolution,
|
||||
)
|
||||
|
||||
context.window_manager.popup_menu_pie(
|
||||
event,
|
||||
|
||||
@@ -43,7 +43,8 @@ from ...common.depth_data import load_depth_metadata, load_depth_result_image
|
||||
from ...common.image_interaction import (
|
||||
ImageRegionGesture,
|
||||
apply_region_settings,
|
||||
draw_region_settings,
|
||||
draw_ai_property,
|
||||
draw_ai_setup,
|
||||
gesture_property,
|
||||
report_image_edit_exception,
|
||||
resolve_image_edit_click,
|
||||
@@ -450,6 +451,7 @@ class CutoutTool(bpy.types.WorkSpaceTool):
|
||||
|
||||
def draw_settings(context, layout, _tool):
|
||||
settings = context.scene.anyimage_settings
|
||||
draw_region_settings(context, layout, "cutout_refine_region")
|
||||
status = draw_ai_setup(layout)
|
||||
draw_ai_property(layout, settings, "cutout_refine_region", status)
|
||||
layout.prop(settings, "cutout_point_spacing")
|
||||
layout.prop(settings, "cutout_generate_normal")
|
||||
draw_ai_property(layout, settings, "cutout_generate_normal", status)
|
||||
|
||||
@@ -109,6 +109,19 @@ def preview_polygon(points):
|
||||
return points[np.argsort(np.arctan2(points[:, 1] - center[1], points[:, 0] - center[0]))]
|
||||
|
||||
|
||||
def interactive_preview_polygon(points, cursor):
|
||||
"""Return the clicked points plus one distinct moving cursor point."""
|
||||
vertices = list(points)
|
||||
if (
|
||||
cursor is not None
|
||||
and vertices
|
||||
and len(vertices) < 4
|
||||
and math.dist(vertices[-1], cursor) >= 2.0
|
||||
):
|
||||
vertices.append(cursor)
|
||||
return preview_polygon(vertices)
|
||||
|
||||
|
||||
def clip_polygon_to_image(points, image_size):
|
||||
"""Clip one polygon to the source image rectangle."""
|
||||
polygon = [tuple(float(value) for value in point) for point in points]
|
||||
@@ -283,7 +296,7 @@ def create_perspective_preview_image(source_image, pixels):
|
||||
alpha=True,
|
||||
)
|
||||
try:
|
||||
image.colorspace_settings.name = source_image.colorspace_settings.name
|
||||
image.colorspace_settings.name = "sRGB"
|
||||
image.alpha_mode = "STRAIGHT"
|
||||
image.pixels.foreach_set(pixels)
|
||||
image.update()
|
||||
@@ -293,6 +306,12 @@ def create_perspective_preview_image(source_image, pixels):
|
||||
return image
|
||||
|
||||
|
||||
def preview_texture_draw_options(blender_version):
|
||||
if tuple(blender_version) >= (5, 0, 0):
|
||||
return {"is_scene_linear_with_rec709_srgb_target": True}
|
||||
return {}
|
||||
|
||||
|
||||
def homography_from_points(source, target):
|
||||
"""Return a projective transform mapping four source points to target."""
|
||||
import numpy as np
|
||||
@@ -526,7 +545,13 @@ class EditImagePerspective(bpy.types.Operator):
|
||||
texture = gpu.texture.from_image(self._preview_image)
|
||||
gpu.state.blend_set("ALPHA")
|
||||
try:
|
||||
draw_texture_2d(texture, (left, bottom), width, height)
|
||||
draw_texture_2d(
|
||||
texture,
|
||||
(left, bottom),
|
||||
width,
|
||||
height,
|
||||
**preview_texture_draw_options(bpy.app.version),
|
||||
)
|
||||
finally:
|
||||
gpu.state.blend_set("NONE")
|
||||
draw_preview_frame(bounds)
|
||||
@@ -542,10 +567,7 @@ class EditImagePerspective(bpy.types.Operator):
|
||||
if self._phase == "ASPECT":
|
||||
self._draw_preview()
|
||||
return
|
||||
vertices = list(self._points)
|
||||
if self._cursor is not None and len(vertices) < 4:
|
||||
vertices.append(self._cursor)
|
||||
vertices = list(preview_polygon(vertices))
|
||||
vertices = list(interactive_preview_polygon(self._points, self._cursor))
|
||||
draw_polygon_fill(vertices)
|
||||
if len(vertices) > 2:
|
||||
vertices.append(vertices[0])
|
||||
|
||||
Reference in New Issue
Block a user