feat: Implement cutout tool and depth generation features
- Added a new cutout tool for selecting regions in images, allowing users to choose mesh shapes based on selected areas. - Introduced depth generation capabilities for rasterized images, including normal data extraction. - Created jobs for generating depth planes and region data, enhancing the AI data generation workflow. - Developed a selection refinement process for cutout regions, improving accuracy in generated outputs. - Established a new region module for handling rasterized image regions and their associated AI generation tasks. - Enhanced node graph builders for asset generation, facilitating smoother integration of new features.
This commit is contained in:
240
src/anyimage/operators/cutout_tool/select_region.py
Normal file
240
src/anyimage/operators/cutout_tool/select_region.py
Normal file
@@ -0,0 +1,240 @@
|
||||
import bpy
|
||||
|
||||
from ...properties import (
|
||||
refine_selection_property,
|
||||
)
|
||||
from ..common.image_data import image_rgba, require_image_empty, video_total_frames
|
||||
from ..common.image_interaction import (
|
||||
apply_region_settings,
|
||||
draw_region_settings,
|
||||
report_image_edit_exception,
|
||||
resolve_image_edit_click,
|
||||
)
|
||||
from ..common.image_interaction import serialize_matrix
|
||||
from ..common.image_interaction import ImageRegionGesture, gesture_property
|
||||
from ..common.image_region import (
|
||||
ImageEditWarning,
|
||||
RegionPath,
|
||||
rasterize_region_path,
|
||||
)
|
||||
|
||||
CUTOUT_DETAIL_MIN = 4
|
||||
CUTOUT_DETAIL_MAX = 1024
|
||||
|
||||
|
||||
def _validate_cutout_region(
|
||||
source_object,
|
||||
region_path,
|
||||
):
|
||||
image = source_object.data
|
||||
rgba = image_rgba(image)
|
||||
rasterize_region_path(
|
||||
tuple(image.size),
|
||||
region_path,
|
||||
source_alpha=rgba[:, :, 3],
|
||||
alpha_threshold=0.1,
|
||||
antialias=True,
|
||||
)
|
||||
|
||||
|
||||
def cutout_mesh_detail(image_size, point_spacing):
|
||||
longest_edge = max(int(value) for value in image_size)
|
||||
detail = round(longest_edge / max(int(point_spacing), 1))
|
||||
return max(CUTOUT_DETAIL_MIN, min(CUTOUT_DETAIL_MAX, detail))
|
||||
|
||||
|
||||
def _pie_button(
|
||||
pie,
|
||||
shape,
|
||||
label,
|
||||
icon,
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_selection,
|
||||
normal_mode,
|
||||
mesh_detail,
|
||||
):
|
||||
operator = pie.operator(
|
||||
"anyimage.cutout_region_to_shape",
|
||||
text=label,
|
||||
icon=icon,
|
||||
)
|
||||
operator.source_object_name = source_name
|
||||
operator.region_path_json = region_path_json
|
||||
operator.shape = shape
|
||||
operator.refine_selection = refine_selection
|
||||
operator.normal_mode = normal_mode
|
||||
operator.mesh_detail = mesh_detail
|
||||
|
||||
|
||||
def _open_shape_pie(
|
||||
context,
|
||||
event,
|
||||
source_object,
|
||||
region_path,
|
||||
refine_selection=False,
|
||||
):
|
||||
source_name = source_object.name
|
||||
region_path_json = region_path.to_json()
|
||||
settings = context.scene.anyimage_settings
|
||||
point_spacing = settings.cutout_point_spacing
|
||||
normal_mode = str(getattr(settings, "cutout_normal_mode", "NONE"))
|
||||
mesh_detail = cutout_mesh_detail(source_object.data.size, point_spacing)
|
||||
|
||||
def draw(menu, _context):
|
||||
pie = menu.layout.menu_pie()
|
||||
_pie_button(
|
||||
pie,
|
||||
"SURFACE",
|
||||
"Surface",
|
||||
"MESH_GRID",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_selection,
|
||||
normal_mode,
|
||||
mesh_detail,
|
||||
)
|
||||
_pie_button(
|
||||
pie,
|
||||
"BALLOON",
|
||||
"Balloon",
|
||||
"MESH_UVSPHERE",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_selection,
|
||||
normal_mode,
|
||||
mesh_detail,
|
||||
)
|
||||
_pie_button(
|
||||
pie,
|
||||
"DEPTH_BALLOON",
|
||||
"Depth Balloon",
|
||||
"META_BALL",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_selection,
|
||||
normal_mode,
|
||||
mesh_detail,
|
||||
)
|
||||
_pie_button(
|
||||
pie,
|
||||
"DEPTH_SURFACE",
|
||||
"Depth Surface",
|
||||
"SURFACE_NCURVE",
|
||||
source_name,
|
||||
region_path_json,
|
||||
refine_selection,
|
||||
normal_mode,
|
||||
mesh_detail,
|
||||
)
|
||||
|
||||
context.window_manager.popup_menu_pie(
|
||||
event,
|
||||
draw,
|
||||
title="Cutout Shape",
|
||||
icon="MESH_DATA",
|
||||
)
|
||||
|
||||
|
||||
class SelectCutoutRegion(ImageRegionGesture, bpy.types.Operator):
|
||||
bl_idname = "anyimage.select_cutout_region"
|
||||
bl_label = "Select Cutout Region"
|
||||
bl_description = "Lasso an Image Empty region, then choose its mesh shape"
|
||||
bl_options = {"INTERNAL"}
|
||||
active_tool_id = "anyimage.cutout_lasso"
|
||||
|
||||
source_object_name: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
source_matrix_data: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
region_path_json: bpy.props.StringProperty(options={"HIDDEN", "SKIP_SAVE"})
|
||||
refine_selection: refine_selection_property()
|
||||
invert: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"})
|
||||
gesture: gesture_property()
|
||||
full_image: bpy.props.BoolProperty(default=False, options={"HIDDEN", "SKIP_SAVE"})
|
||||
|
||||
preview_fill_rule = 'NONZERO'
|
||||
_pie_event = None
|
||||
|
||||
def _set_status(self, context):
|
||||
invert = "On" if self.invert else "Off"
|
||||
context.workspace.status_text_set(
|
||||
f"Cutout Lasso: drag and release for shapes; "
|
||||
f"F toggles Invert ({invert}); RMB or Esc cancels"
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
self._pie_event = event
|
||||
if not self.full_image and event.value != "DOUBLE_CLICK":
|
||||
return super().invoke(context, event)
|
||||
source_object, should_edit = resolve_image_edit_click(
|
||||
context, event, self.report
|
||||
)
|
||||
if not should_edit:
|
||||
return {"FINISHED"} if source_object is not None else {"CANCELLED"}
|
||||
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")
|
||||
return {"CANCELLED"}
|
||||
apply_region_settings(self, context)
|
||||
self.invert = False
|
||||
self.source_object_name = source_object.name
|
||||
self.source_matrix_data = serialize_matrix(source_object.matrix_world)
|
||||
width, height = (int(value) for value in source_object.data.size)
|
||||
self.region_path_json = RegionPath(
|
||||
points=((0, 0), (width, 0), (width, height), (0, height)),
|
||||
fill_rule="NONZERO",
|
||||
).to_json()
|
||||
return self.execute(context)
|
||||
|
||||
def modal(self, context, event):
|
||||
self._pie_event = event
|
||||
return super().modal(context, event)
|
||||
|
||||
def execute(self, context):
|
||||
try:
|
||||
source_object = require_image_empty(self.source_object_name)
|
||||
except RuntimeError as error:
|
||||
self.report({"ERROR"}, str(error))
|
||||
return {"CANCELLED"}
|
||||
try:
|
||||
region_path = RegionPath.from_json(self.region_path_json)
|
||||
_validate_cutout_region(source_object, region_path)
|
||||
except (ImageEditWarning, TypeError, ValueError) as error:
|
||||
report_image_edit_exception(self, error)
|
||||
return {"CANCELLED"}
|
||||
_open_shape_pie(
|
||||
context,
|
||||
self._pie_event,
|
||||
source_object,
|
||||
region_path,
|
||||
self.refine_selection,
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class CutoutTool(bpy.types.WorkSpaceTool):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_context_mode = "OBJECT"
|
||||
bl_idname = "anyimage.cutout_lasso"
|
||||
bl_label = "Cutout"
|
||||
bl_description = "Lasso an Image Empty and choose a mesh shape"
|
||||
bl_icon = "ops.sculpt.lasso_mask"
|
||||
bl_operator = SelectCutoutRegion.bl_idname
|
||||
bl_keymap = (
|
||||
(
|
||||
SelectCutoutRegion.bl_idname,
|
||||
{"type": "LEFTMOUSE", "value": "DOUBLE_CLICK"},
|
||||
{"properties": [("gesture", "LASSO"), ("full_image", True)]},
|
||||
),
|
||||
(
|
||||
SelectCutoutRegion.bl_idname,
|
||||
{"type": "LEFTMOUSE", "value": "PRESS"},
|
||||
{"properties": [("gesture", "LASSO")]},
|
||||
),
|
||||
)
|
||||
|
||||
def draw_settings(context, layout, _tool):
|
||||
settings = context.scene.anyimage_settings
|
||||
layout.prop(settings, "cutout_point_spacing")
|
||||
layout.prop(settings, "cutout_normal_mode")
|
||||
draw_region_settings(context, layout, "cutout_refine_selection")
|
||||
Reference in New Issue
Block a user