feat: Update version to 0.13.0 in project files and adjust dependencies
This commit is contained in:
@@ -484,6 +484,46 @@ def replace_empty_image(source_object, result_image, *, placement_bounds=None):
|
||||
return result_image
|
||||
|
||||
|
||||
def place_empty_image_result(
|
||||
context,
|
||||
source_object,
|
||||
result_image,
|
||||
*,
|
||||
placement_bounds=None,
|
||||
keep_original=False,
|
||||
):
|
||||
"""Place one crop result and optionally preserve its source."""
|
||||
if not keep_original:
|
||||
replace_empty_image(
|
||||
source_object,
|
||||
result_image,
|
||||
placement_bounds=placement_bounds,
|
||||
)
|
||||
return source_object
|
||||
|
||||
result_object = source_object.copy()
|
||||
collections = tuple(source_object.users_collection)
|
||||
if not collections:
|
||||
collections = (context.collection,)
|
||||
try:
|
||||
for collection in collections:
|
||||
collection.objects.link(result_object)
|
||||
replace_empty_image(
|
||||
result_object,
|
||||
result_image,
|
||||
placement_bounds=placement_bounds,
|
||||
)
|
||||
except Exception:
|
||||
bpy.data.objects.remove(result_object, do_unlink=True)
|
||||
raise
|
||||
|
||||
for selected in context.selected_objects:
|
||||
selected.select_set(False)
|
||||
result_object.select_set(True)
|
||||
context.view_layer.objects.active = result_object
|
||||
return result_object
|
||||
|
||||
|
||||
|
||||
def load_color_result_image(
|
||||
source_object,
|
||||
|
||||
@@ -135,7 +135,7 @@ def resolve_image_edit_click(context, event, report):
|
||||
if is_image_empty(active_object):
|
||||
_restore_selection(context, active_object, selected_objects)
|
||||
return active_object, True
|
||||
report({"WARNING"}, "Click an Image Empty to use an Image tool")
|
||||
report({"WARNING"}, "Click an Image Empty to use a Crop tool")
|
||||
return None, False
|
||||
|
||||
|
||||
@@ -151,32 +151,50 @@ def active_view3d_tool_id(context):
|
||||
return getattr(tool, "idname", None)
|
||||
|
||||
|
||||
def refine_selection_setting_name(operator):
|
||||
setting_names = getattr(operator, "refine_selection_settings", {})
|
||||
def _crop_setting_name(operator, mapping_name):
|
||||
setting_names = getattr(operator, mapping_name, {})
|
||||
return setting_names.get(operator.gesture)
|
||||
|
||||
|
||||
def apply_selection_settings(operator, context):
|
||||
def apply_crop_settings(operator, context):
|
||||
settings = context.scene.anyimage_settings
|
||||
setting_name = refine_selection_setting_name(operator)
|
||||
setting_name = _crop_setting_name(operator, "refine_selection_settings")
|
||||
operator.refine_selection = (
|
||||
setting_name is not None and ai_ready() and getattr(settings, setting_name)
|
||||
)
|
||||
setting_name = _crop_setting_name(operator, "invert_settings")
|
||||
if setting_name is not None:
|
||||
operator.invert = bool(getattr(settings, setting_name))
|
||||
setting_name = _crop_setting_name(operator, "keep_original_settings")
|
||||
if setting_name is not None:
|
||||
operator.keep_original = bool(getattr(settings, setting_name))
|
||||
|
||||
|
||||
def draw_selection_operator_settings(layout, operator):
|
||||
if operator.gesture not in {"BOX", "LASSO"}:
|
||||
def draw_crop_operator_settings(layout, operator):
|
||||
if _crop_setting_name(operator, "refine_selection_settings") is not None:
|
||||
status = draw_ai_setup(layout)
|
||||
draw_ai_property(layout, operator, "refine_selection", status)
|
||||
if _crop_setting_name(operator, "invert_settings") is not None:
|
||||
layout.prop(operator, "invert")
|
||||
return
|
||||
status = draw_ai_setup(layout)
|
||||
draw_ai_property(layout, operator, "refine_selection", status)
|
||||
layout.prop(operator, "invert")
|
||||
if _crop_setting_name(operator, "keep_original_settings") is not None:
|
||||
layout.prop(operator, "keep_original")
|
||||
|
||||
|
||||
def draw_selection_settings(context, layout, setting_name):
|
||||
def draw_crop_settings(
|
||||
context,
|
||||
layout,
|
||||
*,
|
||||
keep_original,
|
||||
invert=None,
|
||||
refine_selection=None,
|
||||
):
|
||||
settings = context.scene.anyimage_settings
|
||||
status = draw_ai_setup(layout)
|
||||
draw_ai_property(layout, settings, setting_name, status)
|
||||
if refine_selection is not None:
|
||||
status = draw_ai_setup(layout)
|
||||
draw_ai_property(layout, settings, refine_selection, status)
|
||||
if invert is not None:
|
||||
layout.prop(settings, invert)
|
||||
layout.prop(settings, keep_original)
|
||||
|
||||
|
||||
def dashed_line_segments(vertices, dash_length=4.0, gap_length=4.0):
|
||||
@@ -475,13 +493,11 @@ def draw_polygon_fill(
|
||||
gpu.state.blend_set("NONE")
|
||||
|
||||
|
||||
GESTURE_TOOL_IDS = {
|
||||
"BOX": "anyimage.image_box",
|
||||
"LASSO": "anyimage.image_lasso",
|
||||
"LINE": "anyimage.image_line",
|
||||
"POLYLINE": "anyimage.image_polyline",
|
||||
CROP_TOOL_IDS = {
|
||||
"BOX": "anyimage.crop_box",
|
||||
"LASSO": "anyimage.crop_lasso",
|
||||
"POLYLINE": "anyimage.crop_polyline",
|
||||
}
|
||||
LINE_SNAP_RADIANS = math.radians(5.0)
|
||||
POLYLINE_CLOSE_RADIUS = 8.0
|
||||
LASSO_POINT_DISTANCE = 2.0
|
||||
LASSO_SIMPLIFY_TOLERANCE = 0.75
|
||||
@@ -528,7 +544,6 @@ def gesture_property():
|
||||
items=(
|
||||
("BOX", "Box", "Edit with a rectangular gesture"),
|
||||
("LASSO", "Lasso", "Edit with a freehand gesture"),
|
||||
("LINE", "Line", "Edit one side of a line"),
|
||||
("POLYLINE", "Polyline", "Edit with a clicked polygon"),
|
||||
),
|
||||
default="BOX",
|
||||
@@ -544,62 +559,7 @@ def box_screen_path(start, end):
|
||||
return ((x0, y0), (x1, y0), (x1, y1), (x0, y1))
|
||||
|
||||
|
||||
def snapped_line_endpoint(start, end, snap):
|
||||
if not snap:
|
||||
return tuple(float(value) for value in end)
|
||||
delta_x = float(end[0]) - float(start[0])
|
||||
delta_y = float(end[1]) - float(start[1])
|
||||
length = math.hypot(delta_x, delta_y)
|
||||
if length <= 0.0:
|
||||
return tuple(float(value) for value in end)
|
||||
angle = round(math.atan2(delta_y, delta_x) / LINE_SNAP_RADIANS)
|
||||
angle *= LINE_SNAP_RADIANS
|
||||
return (
|
||||
float(start[0]) + math.cos(angle) * length,
|
||||
float(start[1]) + math.sin(angle) * length,
|
||||
)
|
||||
|
||||
|
||||
def line_half_plane_polygon(start, end, rectangle_size):
|
||||
"""Clip a rectangle to the left side of one directed line."""
|
||||
width, height = (float(value) for value in rectangle_size)
|
||||
if math.dist(start, end) < 2.0:
|
||||
return ()
|
||||
vertices = [(0.0, 0.0), (width, 0.0), (width, height), (0.0, height)]
|
||||
|
||||
def side(point):
|
||||
return (end[0] - start[0]) * (point[1] - start[1]) - (end[1] - start[1]) * (
|
||||
point[0] - start[0]
|
||||
)
|
||||
|
||||
result = []
|
||||
previous = vertices[-1]
|
||||
previous_side = side(previous)
|
||||
for current in vertices:
|
||||
current_side = side(current)
|
||||
if (current_side >= 0.0) != (previous_side >= 0.0):
|
||||
factor = previous_side / (previous_side - current_side)
|
||||
result.append(
|
||||
(
|
||||
previous[0] + (current[0] - previous[0]) * factor,
|
||||
previous[1] + (current[1] - previous[1]) * factor,
|
||||
)
|
||||
)
|
||||
if current_side >= 0.0:
|
||||
result.append(current)
|
||||
previous = current
|
||||
previous_side = current_side
|
||||
return tuple(result)
|
||||
|
||||
|
||||
def image_line_half_plane_polygon(line, image_size):
|
||||
"""Keep the image-space half-plane shown by the screen-space preview."""
|
||||
if len(line) != 2:
|
||||
raise ValueError("Drag a longer line across the image plane")
|
||||
return line_half_plane_polygon(line[1], line[0], image_size)
|
||||
|
||||
|
||||
class ImageSelectionGesture:
|
||||
class CropSelectionGesture:
|
||||
active_tool_id = None
|
||||
_path = None
|
||||
_cursor = None
|
||||
@@ -613,7 +573,7 @@ class ImageSelectionGesture:
|
||||
_region_size = (0, 0)
|
||||
|
||||
def draw(self, _context):
|
||||
draw_selection_operator_settings(self.layout, self)
|
||||
draw_crop_operator_settings(self.layout, self)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@@ -624,10 +584,6 @@ class ImageSelectionGesture:
|
||||
return box_screen_path(self._path[0], self._cursor)
|
||||
if self.gesture == "LASSO":
|
||||
return tuple(self._path)
|
||||
if self.gesture == "LINE":
|
||||
return line_half_plane_polygon(
|
||||
self._path[0], self._cursor, self._region_size
|
||||
)
|
||||
vertices = list(self._path)
|
||||
if self._cursor is not None:
|
||||
vertices.append(self._cursor)
|
||||
@@ -641,10 +597,7 @@ class ImageSelectionGesture:
|
||||
polygon = self._screen_path()
|
||||
if polygon != self._outline_polygon:
|
||||
self._outline_polygon = polygon
|
||||
if self.gesture == "LINE":
|
||||
outline = (self._path[0], self._cursor)
|
||||
else:
|
||||
outline = (*polygon, polygon[0]) if len(polygon) > 1 else ()
|
||||
outline = (*polygon, polygon[0]) if len(polygon) > 1 else ()
|
||||
self._outline_segments = dashed_line_segments(outline)
|
||||
if len(polygon) >= 3:
|
||||
if polygon != self._fill_polygon:
|
||||
@@ -655,9 +608,6 @@ class ImageSelectionGesture:
|
||||
self._fill_triangles,
|
||||
(1.0, 0.25, 0.2, 0.16) if self.invert else (1.0, 1.0, 1.0, 0.16),
|
||||
)
|
||||
if self.gesture == "LINE":
|
||||
draw_dashed_line((), segments=self._outline_segments)
|
||||
return
|
||||
if len(polygon) > 1:
|
||||
draw_dashed_line((), segments=self._outline_segments)
|
||||
if (
|
||||
@@ -694,12 +644,10 @@ class ImageSelectionGesture:
|
||||
invert = "On" if self.invert else "Off"
|
||||
if self.gesture == "POLYLINE":
|
||||
action = "click points; click start or Enter to finish; Backspace removes"
|
||||
elif self.gesture == "LINE":
|
||||
action = "drag line; Ctrl snaps 5°; release to edit"
|
||||
else:
|
||||
action = "drag and release to edit"
|
||||
context.workspace.status_text_set(
|
||||
f"Image {self.gesture.title()}: {action}; F toggles Invert ({invert}); "
|
||||
f"Crop {self.gesture.title()}: {action}; Invert {invert}; "
|
||||
"RMB or Esc cancels"
|
||||
)
|
||||
|
||||
@@ -726,10 +674,9 @@ class ImageSelectionGesture:
|
||||
if source_object is None:
|
||||
return {"CANCELLED"}
|
||||
if video_total_frames(source_object.data) > 1:
|
||||
self.report({"WARNING"}, "Image tools only support a still image")
|
||||
self.report({"WARNING"}, "Crop tools only support a still image")
|
||||
return {"CANCELLED"}
|
||||
apply_selection_settings(self, context)
|
||||
self.invert = False
|
||||
apply_crop_settings(self, context)
|
||||
self.source_object_name = source_object.name
|
||||
self.source_matrix_data = serialize_matrix(source_object.matrix_world)
|
||||
point = (float(event.mouse_region_x), float(event.mouse_region_y))
|
||||
@@ -753,31 +700,18 @@ class ImageSelectionGesture:
|
||||
def _complete(self, context):
|
||||
source_object = require_image_empty(self.source_object_name)
|
||||
matrix = deserialize_matrix(self.source_matrix_data)
|
||||
if self.gesture == "LINE":
|
||||
line = screen_path_to_image_pixels(
|
||||
(self._path[0], self._cursor),
|
||||
context.region,
|
||||
context.region_data,
|
||||
matrix,
|
||||
image_empty_bounds(source_object),
|
||||
tuple(source_object.data.size),
|
||||
)
|
||||
selection_path = image_line_half_plane_polygon(
|
||||
line, tuple(source_object.data.size)
|
||||
)
|
||||
else:
|
||||
path = self._screen_path()
|
||||
if len(path) < 3:
|
||||
self._finish(context)
|
||||
return {"CANCELLED"}
|
||||
selection_path = screen_path_to_image_pixels(
|
||||
path,
|
||||
context.region,
|
||||
context.region_data,
|
||||
matrix,
|
||||
image_empty_bounds(source_object),
|
||||
tuple(source_object.data.size),
|
||||
)
|
||||
path = self._screen_path()
|
||||
if len(path) < 3:
|
||||
self._finish(context)
|
||||
return {"CANCELLED"}
|
||||
selection_path = screen_path_to_image_pixels(
|
||||
path,
|
||||
context.region,
|
||||
context.region_data,
|
||||
matrix,
|
||||
image_empty_bounds(source_object),
|
||||
tuple(source_object.data.size),
|
||||
)
|
||||
selection_path = SelectionPath(
|
||||
points=selection_path,
|
||||
invert=self.invert,
|
||||
@@ -787,18 +721,13 @@ class ImageSelectionGesture:
|
||||
|
||||
def modal(self, context, event):
|
||||
active_tool = active_view3d_tool_id(context)
|
||||
expected_tool = self.active_tool_id or GESTURE_TOOL_IDS[self.gesture]
|
||||
expected_tool = self.active_tool_id or CROP_TOOL_IDS[self.gesture]
|
||||
if active_tool is not None and active_tool != expected_tool:
|
||||
self._finish(context)
|
||||
return {"CANCELLED"}
|
||||
if event.type in {"ESC", "RIGHTMOUSE"} and event.value == "PRESS":
|
||||
self._finish(context)
|
||||
return {"CANCELLED"}
|
||||
if event.type == "F" and event.value == "PRESS":
|
||||
self.invert = not self.invert
|
||||
self._set_status(context)
|
||||
context.area.tag_redraw()
|
||||
return {"RUNNING_MODAL"}
|
||||
if self.gesture == "POLYLINE":
|
||||
return self._modal_polyline(context, event)
|
||||
if event.type == "MOUSEMOVE":
|
||||
@@ -806,8 +735,6 @@ class ImageSelectionGesture:
|
||||
if self.gesture == "LASSO":
|
||||
append_lasso_point(self._path, point)
|
||||
self._cursor = point
|
||||
elif self.gesture == "LINE":
|
||||
self._cursor = snapped_line_endpoint(self._path[0], point, event.ctrl)
|
||||
else:
|
||||
self._cursor = point
|
||||
context.area.tag_redraw()
|
||||
@@ -817,11 +744,6 @@ class ImageSelectionGesture:
|
||||
if self.gesture == "LASSO":
|
||||
append_lasso_point(self._path, point)
|
||||
self._cursor = point
|
||||
elif self.gesture == "LINE":
|
||||
self._cursor = snapped_line_endpoint(self._path[0], point, event.ctrl)
|
||||
if math.dist(self._path[0], self._cursor) < 2.0:
|
||||
self._finish(context)
|
||||
return {"CANCELLED"}
|
||||
else:
|
||||
self._cursor = point
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user