- Implement sync_models.py for preparing and uploading ONNX artifacts. - Create build_node.py for building and validating Blender scripts. - Add docs.py for building and previewing documentation with Mike or MkDocs. - Introduce model_exports.py for exporting PyTorch models to ONNX format. - Develop pack.py for packaging the project with dependencies for different platforms. - Add test_sync_models.py to validate sync_models functionality. - Add test_tool_commands.py to ensure repository tools have correct command scripts. - Initialize tools package with __init__.py.
160 lines
5.5 KiB
Python
160 lines
5.5 KiB
Python
import sys
|
|
from pathlib import Path
|
|
from types import MethodType, SimpleNamespace
|
|
|
|
import bpy
|
|
|
|
SOURCE_ROOT = Path(__file__).parents[1] / "src"
|
|
sys.path.insert(0, str(SOURCE_ROOT))
|
|
|
|
from anyimage.common.image_interaction import ImageRegionGesture
|
|
from anyimage.common.image_region import RegionPath
|
|
from anyimage.operators.cutout_tool import interaction
|
|
from anyimage.operators.cutout_tool import main as cutout_main
|
|
|
|
|
|
class Pie:
|
|
def __init__(self):
|
|
self.calls = []
|
|
|
|
def operator(self, identifier, **options):
|
|
operator = SimpleNamespace()
|
|
self.calls.append((identifier, options, operator))
|
|
return operator
|
|
|
|
|
|
class WindowManager:
|
|
def __init__(self):
|
|
self.pie = Pie()
|
|
|
|
def popup_menu_pie(self, _event, draw, **_options):
|
|
menu = SimpleNamespace(layout=SimpleNamespace(menu_pie=lambda: self.pie))
|
|
draw(menu, None)
|
|
|
|
|
|
def cutout_context(window_manager, point_spacing=32, generate_normal=False):
|
|
settings = SimpleNamespace(
|
|
cutout_point_spacing=point_spacing,
|
|
cutout_generate_normal=generate_normal,
|
|
)
|
|
return SimpleNamespace(
|
|
window_manager=window_manager,
|
|
scene=SimpleNamespace(anyimage_settings=settings),
|
|
)
|
|
|
|
|
|
def cutout_source(name="Source", size=(2048, 1024)):
|
|
return SimpleNamespace(name=name, data=SimpleNamespace(size=size))
|
|
|
|
|
|
def test_cutout_pie_uses_four_semantic_shapes_in_cardinal_order():
|
|
window_manager = WindowManager()
|
|
context = cutout_context(window_manager)
|
|
source = cutout_source()
|
|
|
|
interaction.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] == [
|
|
"SURFACE",
|
|
"BALLOON",
|
|
"DEPTH_BALLOON",
|
|
"DEPTH_SURFACE",
|
|
]
|
|
assert [call[1]["text"] for call in window_manager.pie.calls] == [
|
|
"Surface",
|
|
"Balloon",
|
|
"Depth Balloon",
|
|
"Depth Surface",
|
|
]
|
|
assert all(
|
|
call[0] == "anyimage.cutout_region_to_shape"
|
|
for call in window_manager.pie.calls
|
|
)
|
|
assert not any(call[2].refine_region for call in window_manager.pie.calls)
|
|
assert not any(call[2].generate_normal for call in window_manager.pie.calls)
|
|
assert all(call[2].mesh_resolution == 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, generate_normal=True)
|
|
source = cutout_source(size=(4096, 2048))
|
|
|
|
interaction.open_shape_pie(
|
|
context,
|
|
object(),
|
|
source,
|
|
RegionPath(
|
|
points=((0, 0), (10, 0), (0, 10)),
|
|
invert=True,
|
|
),
|
|
refine_region=True,
|
|
)
|
|
|
|
assert all(call[2].refine_region for call in window_manager.pie.calls)
|
|
assert all(call[2].generate_normal 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_resolution == 256 for call in window_manager.pie.calls)
|
|
|
|
|
|
def test_cutout_normal_space_is_derived_from_shape():
|
|
assert cutout_main.normal_mode_for_shape("DEPTH_SURFACE", False) == "NONE"
|
|
assert cutout_main.normal_mode_for_shape("SURFACE", True) == "TANGENT"
|
|
assert cutout_main.normal_mode_for_shape("BALLOON", True) == "TANGENT"
|
|
assert cutout_main.normal_mode_for_shape("DEPTH_BALLOON", True) == "TANGENT"
|
|
assert cutout_main.normal_mode_for_shape("DEPTH_SURFACE", True) == "OBJECT"
|
|
|
|
|
|
def test_cutout_mesh_detail_tracks_the_source_long_edge_and_clamps():
|
|
assert interaction.cutout_mesh_resolution((4096, 2048), 32) == 128
|
|
assert interaction.cutout_mesh_resolution((16, 8), 4) == 4
|
|
assert interaction.cutout_mesh_resolution((65536, 32768), 4) == 1024
|
|
|
|
|
|
def test_cutout_reuses_image_lasso_gesture_and_double_click_selects_all(monkeypatch):
|
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
|
image = bpy.data.images.new("Source", width=320, height=180)
|
|
source = bpy.data.objects.new("Source", None)
|
|
source.empty_display_type = "IMAGE"
|
|
source.data = image
|
|
bpy.context.collection.objects.link(source)
|
|
window_manager = WindowManager()
|
|
context = cutout_context(window_manager)
|
|
|
|
monkeypatch.setattr(
|
|
cutout_main, "resolve_image_edit_click", lambda *_args: (source, True)
|
|
)
|
|
monkeypatch.setattr(cutout_main, "video_total_frames", lambda _image: 1)
|
|
monkeypatch.setattr(cutout_main, "apply_region_settings", lambda *_args: None)
|
|
monkeypatch.setattr(cutout_main, "validate_cutout_region", lambda *_args: None)
|
|
|
|
operator = SimpleNamespace(
|
|
full_image=True,
|
|
refine_region=False,
|
|
report=lambda *_args: None,
|
|
)
|
|
operator.execute = MethodType(cutout_main.SelectCutoutRegion.execute, operator)
|
|
result = cutout_main.SelectCutoutRegion.invoke(
|
|
operator, context, SimpleNamespace(value="DOUBLE_CLICK")
|
|
)
|
|
|
|
assert issubclass(cutout_main.SelectCutoutRegion, ImageRegionGesture)
|
|
assert result == {"FINISHED"}
|
|
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_resolution == 10
|
|
assert not hasattr(cutout_main, "pending_selection")
|
|
|
|
|
|
def test_cutout_tool_prioritizes_double_click_before_lasso_press():
|
|
assert cutout_main.CutoutTool.bl_keymap[0][1]["value"] == "DOUBLE_CLICK"
|
|
assert cutout_main.CutoutTool.bl_keymap[1][1]["value"] == "PRESS"
|