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:
2026-08-30 23:09:13 +08:00
parent 32bcd45d7b
commit 08309f719d
88 changed files with 4145 additions and 5639 deletions

View File

@@ -8,8 +8,9 @@ import bpy
SOURCE_ROOT = Path(__file__).parents[1] / "src"
sys.path.insert(0, str(SOURCE_ROOT))
from anyimage.operators.common.image_gesture import ImageMaskGesture
from anyimage.operators.cutout_tool import selection_tool as cutout
from anyimage.operators.common.image_interaction import ImageRegionGesture
from anyimage.operators.cutout_tool import select_region as cutout
from anyimage.operators.common.image_region import RegionPath
class Pie:
@@ -31,10 +32,10 @@ class WindowManager:
draw(menu, None)
def cutout_context(window_manager, point_spacing=32, normal_analysis=False):
def cutout_context(window_manager, point_spacing=32, normal_mode="NONE"):
settings = SimpleNamespace(
cutout_point_spacing=point_spacing,
cutout_normal_analysis=normal_analysis,
cutout_normal_mode=normal_mode,
)
return SimpleNamespace(
window_manager=window_manager,
@@ -51,10 +52,15 @@ def test_cutout_pie_uses_four_semantic_shapes_in_cardinal_order():
context = cutout_context(window_manager)
source = cutout_source()
cutout._open_shape_pie(context, object(), source, ((0, 0), (10, 0), (0, 10)))
cutout._open_shape_pie(
context,
object(),
source,
RegionPath(points=((0, 0), (10, 0), (0, 10))),
)
assert [call[2].shape for call in window_manager.pie.calls] == [
"CUTOUT",
"SURFACE",
"BALLOON",
"DEPTH_BALLOON",
"DEPTH_SURFACE",
@@ -66,18 +72,18 @@ def test_cutout_pie_uses_four_semantic_shapes_in_cardinal_order():
"Depth Surface",
]
assert all(
call[0] == "anyimage.convert_cutout_shape" for call in window_manager.pie.calls
call[0] == "anyimage.cutout_region_to_shape"
for call in window_manager.pie.calls
)
assert not any(call[2].refine_selection for call in window_manager.pie.calls)
assert not any(call[2].normal_analysis for call in window_manager.pie.calls)
assert not any(call[2].invert for call in window_manager.pie.calls)
assert all(call[2].normal_mode == "NONE" for call in window_manager.pie.calls)
assert all(call[2].mesh_detail == 64 for call in window_manager.pie.calls)
def test_cutout_pie_passes_image_lasso_options_to_every_shape():
window_manager = WindowManager()
context = cutout_context(
window_manager, point_spacing=16, normal_analysis=True
window_manager, point_spacing=16, normal_mode="OBJECT"
)
source = cutout_source(size=(4096, 2048))
@@ -85,14 +91,19 @@ def test_cutout_pie_passes_image_lasso_options_to_every_shape():
context,
object(),
source,
((0, 0), (10, 0), (0, 10)),
RegionPath(
points=((0, 0), (10, 0), (0, 10)),
invert=True,
),
refine_selection=True,
invert=True,
)
assert all(call[2].refine_selection for call in window_manager.pie.calls)
assert all(call[2].normal_analysis for call in window_manager.pie.calls)
assert all(call[2].invert for call in window_manager.pie.calls)
assert all(call[2].normal_mode == "OBJECT" for call in window_manager.pie.calls)
assert all(
RegionPath.from_json(call[2].region_path_json).invert
for call in window_manager.pie.calls
)
assert all(call[2].mesh_detail == 256 for call in window_manager.pie.calls)
@@ -116,8 +127,8 @@ def test_cutout_reuses_image_lasso_gesture_and_double_click_selects_all(monkeypa
cutout, "resolve_image_edit_click", lambda *_args: (source, True)
)
monkeypatch.setattr(cutout, "video_total_frames", lambda _image: 1)
monkeypatch.setattr(cutout, "apply_image_mask_settings", lambda *_args: None)
monkeypatch.setattr(cutout, "selected_image_rgba", lambda *_args: object())
monkeypatch.setattr(cutout, "apply_region_settings", lambda *_args: None)
monkeypatch.setattr(cutout, "_validate_cutout_region", lambda *_args: None)
operator = SimpleNamespace(
full_image=True,
@@ -129,10 +140,12 @@ def test_cutout_reuses_image_lasso_gesture_and_double_click_selects_all(monkeypa
operator, context, SimpleNamespace(value="DOUBLE_CLICK")
)
assert issubclass(cutout.SelectCutoutRegion, ImageMaskGesture)
assert issubclass(cutout.SelectCutoutRegion, ImageRegionGesture)
assert result == {"FINISHED"}
selection = json.loads(window_manager.pie.calls[0][2].selection_data)
assert selection == [[0, 0], [320, 0], [320, 180], [0, 180]]
region_path = RegionPath.from_json(
window_manager.pie.calls[0][2].region_path_json
)
assert region_path.points == ((0, 0), (320, 0), (320, 180), (0, 180))
assert window_manager.pie.calls[0][2].mesh_detail == 10
assert not hasattr(cutout, "pending_selection")