feat(cutout_tool): Implement cutout region to shape functionality
- Added interaction helpers for cutout region validation and shape menu interaction. - Created main operator for converting image regions into cutout shapes, including AI artifact generation. - Introduced object creation logic for cutout shapes, handling depth and normal textures. - Defined cutout shape identifiers and node groups for various shape types. - Implemented depth texture writing and prediction artifact generation for MoGe-2. - Developed server jobs for generating cutout artifacts, depth planes, and refining image regions. - Added region refinement logic to enhance gesture masks using BEN2.
This commit is contained in:
@@ -84,21 +84,27 @@ class AddonTest(unittest.TestCase):
|
||||
cls.addon = importlib.import_module("anyimage")
|
||||
cls.job_runtime = importlib.import_module("anyimage.runtime")
|
||||
cls.model_operators = importlib.import_module("anyimage.operators.ai_setup")
|
||||
cls.image_depth = importlib.import_module("anyimage.operators.common.mesh_result")
|
||||
cls.image_depth = importlib.import_module(
|
||||
"anyimage.operators.common.mesh_result"
|
||||
)
|
||||
cls.image_data = importlib.import_module("anyimage.operators.common.image_data")
|
||||
cls.object_data = importlib.import_module("anyimage.operators.common.mesh_result")
|
||||
cls.object_data = importlib.import_module(
|
||||
"anyimage.operators.common.mesh_result"
|
||||
)
|
||||
cls.debug = importlib.import_module("anyimage.operators.debug")
|
||||
cls.mesh_plane = importlib.import_module("anyimage.operators.convert_to_plane.mesh_plane")
|
||||
cls.mesh = importlib.import_module("anyimage.operators.convert_to_plane.conversion")
|
||||
cls.mesh_plane = importlib.import_module(
|
||||
"anyimage.operators.convert_to_plane.mesh_plane"
|
||||
)
|
||||
cls.mesh = importlib.import_module(
|
||||
"anyimage.operators.convert_to_plane.conversion"
|
||||
)
|
||||
cls.cutout_conversion = importlib.import_module(
|
||||
"anyimage.operators.cutout_tool.region_to_shape"
|
||||
"anyimage.operators.cutout_tool.main"
|
||||
)
|
||||
cls.cutout_build = importlib.import_module(
|
||||
"anyimage.operators.cutout_tool.region_to_shape"
|
||||
)
|
||||
cls.mesh_shapes = importlib.import_module(
|
||||
"anyimage.operators.cutout_tool.region_to_shape"
|
||||
"anyimage.operators.cutout_tool.main"
|
||||
)
|
||||
cls.mesh_shapes = importlib.import_module("anyimage.operators.cutout_tool.main")
|
||||
cls.image_background = importlib.import_module(
|
||||
"anyimage.operators.remove_background"
|
||||
)
|
||||
@@ -116,9 +122,7 @@ class AddonTest(unittest.TestCase):
|
||||
"anyimage.operators.common.image_interaction"
|
||||
)
|
||||
cls.image_box = importlib.import_module("anyimage.operators.image_tool.box")
|
||||
cls.image_lasso = importlib.import_module(
|
||||
"anyimage.operators.image_tool.lasso"
|
||||
)
|
||||
cls.image_lasso = importlib.import_module("anyimage.operators.image_tool.lasso")
|
||||
cls.image_line = importlib.import_module("anyimage.operators.image_tool.line")
|
||||
cls.image_polyline = importlib.import_module(
|
||||
"anyimage.operators.image_tool.polyline"
|
||||
@@ -127,10 +131,10 @@ class AddonTest(unittest.TestCase):
|
||||
"anyimage.operators.perspective_tool.edit_perspective"
|
||||
)
|
||||
cls.cutout_geometry = importlib.import_module(
|
||||
"anyimage.operators.cutout_tool.geometry.triangulation"
|
||||
"anyimage.operators.cutout_tool.geometry"
|
||||
)
|
||||
cls.cutout_depth = importlib.import_module(
|
||||
"anyimage.operators.cutout_tool.geometry.depth_calibration"
|
||||
"anyimage.operators.cutout_tool.geometry"
|
||||
)
|
||||
cls.image_interaction = importlib.import_module(
|
||||
"anyimage.operators.common.image_interaction"
|
||||
@@ -525,7 +529,7 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertNotIn("stage", controller.post_status)
|
||||
self.assertNotIn("stages", controller.post_status)
|
||||
|
||||
def test_conversion_model_lists_moge2_before_da3(self):
|
||||
def test_production_geometry_models_exclude_da3(self):
|
||||
properties = importlib.import_module("anyimage.properties")
|
||||
catalog = (
|
||||
{
|
||||
@@ -571,14 +575,12 @@ class AddonTest(unittest.TestCase):
|
||||
)
|
||||
with patch.object(properties, "model_catalog", return_value=catalog):
|
||||
depth_items = properties.depth_plane_model_enum_items(None, None)
|
||||
mesh_depth_items = properties.mesh_depth_model_enum_items(None, None)
|
||||
mesh_depth_items = properties.geometry_model_enum_items(None, None)
|
||||
|
||||
expected = (
|
||||
"MOGE2_VITS_NORMAL",
|
||||
"MOGE2_VITB_NORMAL",
|
||||
"MOGE2_VITL_NORMAL",
|
||||
"SMALL",
|
||||
"BASE",
|
||||
)
|
||||
self.assertEqual(tuple(item[0] for item in depth_items[:-1]), expected)
|
||||
self.assertEqual(tuple(item[0] for item in mesh_depth_items), expected)
|
||||
@@ -687,11 +689,10 @@ class AddonTest(unittest.TestCase):
|
||||
self.model_operators.OpenAIEnvironmentSettings,
|
||||
self.addon.CLASSES,
|
||||
)
|
||||
|
||||
def test_global_ai_input_size_defaults_and_clamps(self):
|
||||
preference_module = self.addon.preferences
|
||||
preferences = self.fake_bpy.context.preferences.addons[
|
||||
"anyimage"
|
||||
].preferences
|
||||
preferences = self.fake_bpy.context.preferences.addons["anyimage"].preferences
|
||||
|
||||
self.assertEqual(
|
||||
preference_module.configured_max_ai_input_size(),
|
||||
@@ -708,6 +709,25 @@ class AddonTest(unittest.TestCase):
|
||||
256,
|
||||
)
|
||||
|
||||
def test_cutout_mesh_resolution_defaults_and_clamps_independently(self):
|
||||
preference_module = self.addon.preferences
|
||||
preferences = self.fake_bpy.context.preferences.addons["anyimage"].preferences
|
||||
|
||||
self.assertEqual(
|
||||
preference_module.configured_max_cutout_mesh_resolution(),
|
||||
1024,
|
||||
)
|
||||
preferences.max_cutout_mesh_resolution = 2048
|
||||
self.assertEqual(
|
||||
preference_module.configured_max_cutout_mesh_resolution(),
|
||||
1024,
|
||||
)
|
||||
preferences.max_cutout_mesh_resolution = 1
|
||||
self.assertEqual(
|
||||
preference_module.configured_max_cutout_mesh_resolution(),
|
||||
4,
|
||||
)
|
||||
|
||||
def test_preferences_draws_upscale_model_download(self):
|
||||
preference_module = importlib.import_module("anyimage.preferences")
|
||||
downloads = []
|
||||
@@ -742,7 +762,7 @@ class AddonTest(unittest.TestCase):
|
||||
|
||||
preferences = preference_module.AnyImagePreferences()
|
||||
preferences.layout = Layout()
|
||||
preferences.mesh_depth_model = "BASE"
|
||||
preferences.geometry_model = "MOGE2_VITS_NORMAL"
|
||||
preferences.upscale_model = "SAFMN_REAL_X4"
|
||||
catalog = tuple(
|
||||
{
|
||||
@@ -781,7 +801,7 @@ class AddonTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(
|
||||
[download.model for download in downloads],
|
||||
["BASE", "BEN2_BASE", "SAFMN_REAL_X4"],
|
||||
["MOGE2_VITS_NORMAL", "BEN2_BASE", "SAFMN_REAL_X4"],
|
||||
)
|
||||
self.assertEqual(
|
||||
[
|
||||
@@ -815,10 +835,10 @@ class AddonTest(unittest.TestCase):
|
||||
("anyimage_preferences_cutout", False),
|
||||
],
|
||||
)
|
||||
self.assertIn("DA3 models cannot generate normals", labels)
|
||||
self.assertNotIn("Normal Model", labels)
|
||||
self.assertIn("upscale_model", properties)
|
||||
self.assertIn("max_ai_input_size", properties)
|
||||
self.assertIn("max_cutout_mesh_resolution", properties)
|
||||
self.assertIn("balloon_profile_algorithm", properties)
|
||||
balloon_profile = preference_module.AnyImagePreferences.__annotations__[
|
||||
"balloon_profile_algorithm"
|
||||
@@ -887,8 +907,8 @@ class AddonTest(unittest.TestCase):
|
||||
return_value=(1, 0, 24, True),
|
||||
),
|
||||
patch.object(self.mesh, "create_image_material", return_value="Material"),
|
||||
patch.object(self.mesh, "copy_color_image", return_value="Color"),
|
||||
patch.object(self.mesh, "build_mesh_plane") as build_mesh,
|
||||
patch.object(self.mesh, "create_color_image", return_value="Color"),
|
||||
patch.object(self.mesh, "create_plane_object") as build_mesh,
|
||||
):
|
||||
result = self.mesh.ConvertToPlane.execute(operator, context)
|
||||
|
||||
@@ -905,9 +925,7 @@ class AddonTest(unittest.TestCase):
|
||||
operator.resolution_level = 5
|
||||
operator.max_input_size = 1536
|
||||
context = SimpleNamespace(
|
||||
scene=SimpleNamespace(
|
||||
anyimage_settings=SimpleNamespace(video_frames=1)
|
||||
)
|
||||
scene=SimpleNamespace(anyimage_settings=SimpleNamespace(video_frames=1))
|
||||
)
|
||||
with (
|
||||
patch.object(self.mesh, "require_environment"),
|
||||
@@ -924,8 +942,12 @@ class AddonTest(unittest.TestCase):
|
||||
def test_surface_and_balloon_without_ai_build_locally(self):
|
||||
import numpy as np
|
||||
|
||||
source = SimpleNamespace(data=SimpleNamespace(size=(10, 10)), select_set=lambda _value: None)
|
||||
context = SimpleNamespace(view_layer=SimpleNamespace(objects=SimpleNamespace(active=None)))
|
||||
source = SimpleNamespace(
|
||||
data=SimpleNamespace(size=(10, 10)), select_set=lambda _value: None
|
||||
)
|
||||
context = SimpleNamespace(
|
||||
view_layer=SimpleNamespace(objects=SimpleNamespace(active=None))
|
||||
)
|
||||
region_mask = self.cutout_conversion.RegionMask(
|
||||
np.ones((8, 8), dtype=np.float32), (1, 1, 9, 9)
|
||||
)
|
||||
@@ -938,25 +960,46 @@ class AddonTest(unittest.TestCase):
|
||||
{"points": [[1, 1], [9, 1], [9, 9], [1, 9]]}
|
||||
),
|
||||
shape=shape,
|
||||
mesh_detail=64,
|
||||
normal_mode="NONE",
|
||||
refine_selection=False,
|
||||
mesh_resolution=64,
|
||||
generate_normal=False,
|
||||
refine_region=False,
|
||||
report=lambda *_args: None,
|
||||
_needs_generation=lambda: False,
|
||||
)
|
||||
with (
|
||||
patch.object(self.cutout_conversion, "require_image_empty", return_value=source),
|
||||
patch.object(self.cutout_conversion, "video_total_frames", return_value=1),
|
||||
patch.object(self.cutout_conversion, "image_rgba", return_value=source_rgba),
|
||||
patch.object(
|
||||
self.cutout_conversion,
|
||||
"require_image_empty",
|
||||
return_value=source,
|
||||
),
|
||||
patch.object(
|
||||
self.cutout_conversion, "video_total_frames", return_value=1
|
||||
),
|
||||
patch.object(
|
||||
self.cutout_conversion, "image_rgba", return_value=source_rgba
|
||||
),
|
||||
patch.object(
|
||||
self.cutout_conversion,
|
||||
"rasterize_region_path",
|
||||
return_value=region_mask,
|
||||
),
|
||||
patch.object(self.cutout_conversion, "configured_max_ai_input_size", return_value=2048),
|
||||
patch.object(self.cutout_conversion, "region_to_shape") as convert,
|
||||
patch.object(
|
||||
self.cutout_conversion,
|
||||
"configured_max_ai_input_size",
|
||||
return_value=2048,
|
||||
),
|
||||
patch.object(
|
||||
self.cutout_conversion,
|
||||
"configured_max_cutout_mesh_resolution",
|
||||
return_value=1024,
|
||||
),
|
||||
patch.object(
|
||||
self.cutout_conversion, "create_cutout_shape"
|
||||
) as convert,
|
||||
):
|
||||
result = self.cutout_conversion.CutoutRegionToShape.execute(operator, context)
|
||||
result = self.cutout_conversion.CutoutRegionToShape.execute(
|
||||
operator, context
|
||||
)
|
||||
|
||||
self.assertEqual(result, {"FINISHED"})
|
||||
convert.assert_called_once_with(
|
||||
@@ -975,16 +1018,24 @@ class AddonTest(unittest.TestCase):
|
||||
operator.shape = "DEPTH_SURFACE"
|
||||
operator.model = self.addon.properties.DEFAULT_MOGE2_MODEL_KEY
|
||||
operator.resolution_level = 5
|
||||
operator.max_input_size = 2048
|
||||
operator.normal_mode = "OBJECT"
|
||||
operator.refine_selection = True
|
||||
context = SimpleNamespace(scene=SimpleNamespace(anyimage_settings=SimpleNamespace()))
|
||||
operator.max_ai_input_size = 2048
|
||||
operator.generate_normal = True
|
||||
operator.refine_region = True
|
||||
context = SimpleNamespace(
|
||||
scene=SimpleNamespace(anyimage_settings=SimpleNamespace())
|
||||
)
|
||||
with (
|
||||
patch.object(self.cutout_conversion, "require_environment"),
|
||||
patch.object(self.cutout_conversion, "require_model") as require_model,
|
||||
patch.object(self.cutout_conversion, "production_device", return_value="CPU"),
|
||||
patch.object(
|
||||
self.cutout_conversion, "production_device", return_value="CPU"
|
||||
),
|
||||
patch.object(self.cutout_conversion, "source_start_frame", return_value=1),
|
||||
patch.object(self.cutout_conversion, "moge2_parameters", return_value={"model": operator.model, "resolution_level": 5}),
|
||||
patch.object(
|
||||
self.cutout_conversion,
|
||||
"moge2_parameters",
|
||||
return_value={"model": operator.model, "resolution_level": 5},
|
||||
),
|
||||
):
|
||||
parameters = operator.request(context)
|
||||
|
||||
@@ -993,27 +1044,27 @@ class AddonTest(unittest.TestCase):
|
||||
[self.addon.properties.BEN2_MODEL_KEY, operator.model],
|
||||
)
|
||||
self.assertTrue(parameters["refine_region"])
|
||||
self.assertTrue(parameters["output_depth"])
|
||||
self.assertEqual(parameters["depth_mode"], "VECTOR")
|
||||
self.assertEqual(parameters["normal_mode"], "OBJECT")
|
||||
self.assertNotIn("polygon", parameters)
|
||||
self.assertNotIn("shape", parameters)
|
||||
self.assertNotIn("normal_analysis", parameters)
|
||||
|
||||
def test_cutout_moge_settings_falls_back_from_da3(self):
|
||||
def test_cutout_geometry_settings_use_configured_moge(self):
|
||||
preferences = SimpleNamespace(
|
||||
mesh_depth_model="DA3_BASE",
|
||||
mesh_resolution_level=7,
|
||||
geometry_model="MOGE2_VITB_NORMAL",
|
||||
geometry_resolution_level=7,
|
||||
)
|
||||
with patch.object(
|
||||
self.cutout_conversion,
|
||||
"addon_preferences",
|
||||
return_value=preferences,
|
||||
):
|
||||
settings = self.cutout_conversion.cutout_moge_settings(None)
|
||||
settings = self.cutout_conversion.cutout_geometry_settings(None)
|
||||
|
||||
self.assertEqual(
|
||||
settings,
|
||||
(self.addon.properties.DEFAULT_MOGE2_MODEL_KEY, 7),
|
||||
("MOGE2_VITB_NORMAL", 7),
|
||||
)
|
||||
|
||||
def test_image_menu_keeps_ai_features_clickable_and_offers_models(self):
|
||||
@@ -1321,9 +1372,7 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertTrue(nonzero[3, 5])
|
||||
|
||||
def preview_area(fill_rule):
|
||||
triangles = self.image_edit_common.scanline_fill_triangles(
|
||||
path, fill_rule
|
||||
)
|
||||
triangles = self.image_edit_common.scanline_fill_triangles(path, fill_rule)
|
||||
return sum(
|
||||
abs(
|
||||
self.image_edit_common._triangle_cross(
|
||||
@@ -1336,8 +1385,8 @@ class AddonTest(unittest.TestCase):
|
||||
for index in range(0, len(triangles), 3)
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(preview_area('EVEN_ODD'), 36.0)
|
||||
self.assertAlmostEqual(preview_area('NONZERO'), 54.0)
|
||||
self.assertAlmostEqual(preview_area("EVEN_ODD"), 36.0)
|
||||
self.assertAlmostEqual(preview_area("NONZERO"), 54.0)
|
||||
|
||||
def test_expected_empty_image_edits_are_reported_as_warnings(self):
|
||||
reports = []
|
||||
@@ -1370,7 +1419,7 @@ class AddonTest(unittest.TestCase):
|
||||
def test_image_edit_tools_and_operators_are_registered(self):
|
||||
operator = self.image_mask.EditImageMask
|
||||
self.assertIn(operator, self.addon.CLASSES)
|
||||
self.assertIn(self.image_mask.RefineImageMask, self.addon.CLASSES)
|
||||
self.assertIn(self.image_mask.RefineImageRegion, self.addon.CLASSES)
|
||||
self.assertIn(self.image_mask.ActivateImageTool, self.addon.CLASSES)
|
||||
tool_cases = (
|
||||
(self.image_box.ImageBoxTool, "BOX", "ops.sculpt.border_mask"),
|
||||
@@ -1391,24 +1440,36 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertIn("draw_settings", self.image_box.ImageBoxTool.__dict__)
|
||||
self.assertIn("draw_settings", self.image_lasso.ImageLassoTool.__dict__)
|
||||
context = SimpleNamespace(scene=SimpleNamespace(anyimage_settings=object()))
|
||||
layout = SimpleNamespace(prop=lambda *_args: None)
|
||||
drawn_settings = []
|
||||
layout = SimpleNamespace(
|
||||
prop=lambda _settings, name: drawn_settings.append(name)
|
||||
)
|
||||
with patch.object(self.image_box, "draw_region_settings") as draw:
|
||||
self.image_box.ImageBoxTool.draw_settings(context, layout, None)
|
||||
draw.assert_called_once_with(
|
||||
context, layout, "image_box_refine_selection"
|
||||
)
|
||||
draw.assert_called_once_with(context, layout, "image_box_refine_region")
|
||||
with patch.object(self.image_lasso, "draw_region_settings") as draw:
|
||||
self.image_lasso.ImageLassoTool.draw_settings(context, layout, None)
|
||||
draw.assert_called_once_with(
|
||||
context, layout, "image_lasso_refine_selection"
|
||||
)
|
||||
draw.assert_called_once_with(context, layout, "image_lasso_refine_region")
|
||||
cutout_tool = importlib.import_module(
|
||||
"anyimage.operators.cutout_tool"
|
||||
).CutoutTool
|
||||
cutout_module = sys.modules[cutout_tool.__module__]
|
||||
with patch.object(cutout_module, "draw_region_settings") as draw:
|
||||
drawn_settings.clear()
|
||||
with patch.object(
|
||||
cutout_module,
|
||||
"draw_region_settings",
|
||||
side_effect=lambda _context, _layout, name: drawn_settings.append(name),
|
||||
) as draw:
|
||||
cutout_tool.draw_settings(context, layout, None)
|
||||
draw.assert_called_once_with(context, layout, "cutout_refine_selection")
|
||||
draw.assert_called_once_with(context, layout, "cutout_refine_region")
|
||||
self.assertEqual(
|
||||
drawn_settings,
|
||||
[
|
||||
"cutout_refine_region",
|
||||
"cutout_point_spacing",
|
||||
"cutout_generate_normal",
|
||||
],
|
||||
)
|
||||
self.assertNotIn("draw_settings", self.image_line.ImageLineTool.__dict__)
|
||||
self.assertNotIn(
|
||||
"draw_settings", self.image_polyline.ImagePolylineTool.__dict__
|
||||
@@ -1419,51 +1480,69 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertNotIn("padding", operator.__annotations__)
|
||||
settings = self.addon.properties.AnyImageSettings.__annotations__
|
||||
for name in (
|
||||
"cutout_refine_selection",
|
||||
"image_box_refine_selection",
|
||||
"image_lasso_refine_selection",
|
||||
"cutout_refine_region",
|
||||
"image_box_refine_region",
|
||||
"image_lasso_refine_region",
|
||||
):
|
||||
self.assertFalse(settings[name]["default"])
|
||||
self.assertNotIn("refine_selection", settings)
|
||||
self.assertNotIn("image_edit_refine_selection", settings)
|
||||
self.assertNotIn("refine_region", settings)
|
||||
self.assertNotIn("image_edit_refine_region", settings)
|
||||
self.assertEqual(settings["cutout_point_spacing"]["default"], 32)
|
||||
self.assertEqual(settings["cutout_point_spacing"]["min"], 4)
|
||||
self.assertEqual(settings["cutout_point_spacing"]["max"], 32)
|
||||
self.assertEqual(settings["cutout_normal_mode"]["default"], "NONE")
|
||||
self.assertFalse(settings["cutout_generate_normal"]["default"])
|
||||
self.assertEqual(settings["cutout_refine_region"]["name"], "Fit to Foreground")
|
||||
self.assertEqual(
|
||||
settings["cutout_point_spacing"]["name"], "Mesh Sampling Spacing"
|
||||
)
|
||||
self.assertEqual(
|
||||
settings["cutout_generate_normal"]["name"],
|
||||
"Generate Normal Map",
|
||||
)
|
||||
self.assertNotIn("image_extract_continuous", settings)
|
||||
self.assertNotIn("image_extract_padding", settings)
|
||||
self.assertNotIn("image_extract_remove_background", settings)
|
||||
def test_refine_selection_is_independent_for_each_area_tool(self):
|
||||
|
||||
def test_refine_region_is_independent_for_each_area_tool(self):
|
||||
settings = SimpleNamespace(
|
||||
cutout_refine_selection=True,
|
||||
image_box_refine_selection=False,
|
||||
image_lasso_refine_selection=True,
|
||||
cutout_refine_region=True,
|
||||
image_box_refine_region=False,
|
||||
image_lasso_refine_region=True,
|
||||
)
|
||||
context = SimpleNamespace(scene=SimpleNamespace(anyimage_settings=settings))
|
||||
with patch.object(self.image_edit_common, "ai_ready", return_value=True):
|
||||
for gesture in ("LINE", "POLYLINE"):
|
||||
operator = SimpleNamespace(
|
||||
gesture=gesture,
|
||||
refine_selection=True,
|
||||
refine_region=True,
|
||||
)
|
||||
self.image_edit_common.apply_region_settings(operator, context)
|
||||
self.assertFalse(operator.refine_selection)
|
||||
self.assertFalse(operator.refine_region)
|
||||
|
||||
box = SimpleNamespace(gesture="BOX", refine_selection=True)
|
||||
box = SimpleNamespace(
|
||||
gesture="BOX",
|
||||
refine_region=True,
|
||||
refine_region_settings={"BOX": "image_box_refine_region"},
|
||||
)
|
||||
self.image_edit_common.apply_region_settings(box, context)
|
||||
self.assertFalse(box.refine_selection)
|
||||
self.assertFalse(box.refine_region)
|
||||
|
||||
lasso = SimpleNamespace(gesture="LASSO", refine_selection=False)
|
||||
lasso = SimpleNamespace(
|
||||
gesture="LASSO",
|
||||
refine_region=False,
|
||||
refine_region_settings={"LASSO": "image_lasso_refine_region"},
|
||||
)
|
||||
self.image_edit_common.apply_region_settings(lasso, context)
|
||||
self.assertTrue(lasso.refine_selection)
|
||||
self.assertTrue(lasso.refine_region)
|
||||
|
||||
cutout = SimpleNamespace(
|
||||
gesture="LASSO",
|
||||
active_tool_id="anyimage.cutout_lasso",
|
||||
refine_selection=False,
|
||||
refine_region=False,
|
||||
refine_region_settings={"LASSO": "cutout_refine_region"},
|
||||
)
|
||||
self.image_edit_common.apply_region_settings(cutout, context)
|
||||
self.assertTrue(cutout.refine_selection)
|
||||
self.assertTrue(cutout.refine_region)
|
||||
|
||||
def test_image_edit_first_click_selects_another_image_without_editing(self):
|
||||
selected = []
|
||||
@@ -1652,14 +1731,14 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertAlmostEqual(area, 10.0)
|
||||
|
||||
def test_image_mask_fill_uses_blender_geometry_without_stopping_the_draw(self):
|
||||
mathutils = ModuleType('mathutils')
|
||||
mathutils = ModuleType("mathutils")
|
||||
mathutils.Vector = lambda values: values
|
||||
mathutils.geometry = SimpleNamespace(
|
||||
tessellate_polygon=lambda _loops: (
|
||||
((0.0, 0.0, 0.0), (2.0, 0.0, 0.0), (0.0, 2.0, 0.0)),
|
||||
)
|
||||
)
|
||||
with patch.dict(sys.modules, {'mathutils': mathutils}):
|
||||
with patch.dict(sys.modules, {"mathutils": mathutils}):
|
||||
triangles = self.image_edit_common.triangulated_polygon_vertices(
|
||||
((0, 0), (2, 0), (0, 2))
|
||||
)
|
||||
@@ -1670,12 +1749,12 @@ class AddonTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_image_mask_fill_falls_back_when_blender_tessellation_rejects_path(self):
|
||||
mathutils = ModuleType('mathutils')
|
||||
mathutils = ModuleType("mathutils")
|
||||
mathutils.Vector = lambda values: values
|
||||
mathutils.geometry = SimpleNamespace(
|
||||
tessellate_polygon=lambda _loops: (_ for _ in ()).throw(TypeError())
|
||||
)
|
||||
with patch.dict(sys.modules, {'mathutils': mathutils}):
|
||||
with patch.dict(sys.modules, {"mathutils": mathutils}):
|
||||
triangles = self.image_edit_common.triangulated_polygon_vertices(
|
||||
((0, 0), (2, 0), (0, 2))
|
||||
)
|
||||
@@ -1684,9 +1763,7 @@ class AddonTest(unittest.TestCase):
|
||||
|
||||
def test_image_mask_preview_fills_a_self_intersecting_lasso(self):
|
||||
polygon = ((0, 0), (4, 4), (0, 4), (4, 0))
|
||||
triangles = self.image_edit_common.preview_fill_triangles(
|
||||
polygon, 'NONZERO'
|
||||
)
|
||||
triangles = self.image_edit_common.preview_fill_triangles(polygon, "NONZERO")
|
||||
area = sum(
|
||||
abs(
|
||||
self.image_edit_common._triangle_cross(
|
||||
@@ -1788,9 +1865,10 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertNotIn("image_perspective_aspect_ratio", settings)
|
||||
self.assertNotIn("aspect_mode", operator.__annotations__)
|
||||
self.assertIn("aspect_ratio", operator.__annotations__)
|
||||
self.assertNotIn("refine_selection", operator.__annotations__)
|
||||
self.assertNotIn("refine_region", operator.__annotations__)
|
||||
self.assertNotIn("continuous", operator.__annotations__)
|
||||
self.assertNotIn("padding", operator.__annotations__)
|
||||
|
||||
def test_image_box_builds_a_rectangular_screen_path(self):
|
||||
self.assertEqual(
|
||||
self.image_gesture.box_screen_path((2, 3), (8, 9)),
|
||||
@@ -1830,9 +1908,7 @@ class AddonTest(unittest.TestCase):
|
||||
return_value=polygon,
|
||||
),
|
||||
):
|
||||
result = self.image_gesture.ImageRegionGesture._complete(
|
||||
operator, context
|
||||
)
|
||||
result = self.image_gesture.ImageRegionGesture._complete(operator, context)
|
||||
|
||||
self.assertEqual(result, {"FINISHED"})
|
||||
self.assertEqual(
|
||||
@@ -1854,12 +1930,12 @@ class AddonTest(unittest.TestCase):
|
||||
def test_image_lasso_preview_uses_fill_and_a_closed_dashed_outline(self):
|
||||
polygon = ((1.0, 2.0), (8.0, 3.0), (4.0, 9.0))
|
||||
operator = SimpleNamespace(
|
||||
gesture='LASSO',
|
||||
gesture="LASSO",
|
||||
invert=False,
|
||||
_path=[polygon[0]],
|
||||
_area_pointer=1,
|
||||
_region_pointer=2,
|
||||
preview_fill_rule='NONZERO',
|
||||
preview_fill_rule="NONZERO",
|
||||
_fill_polygon=None,
|
||||
_fill_triangles=(),
|
||||
_outline_polygon=None,
|
||||
@@ -1867,33 +1943,33 @@ class AddonTest(unittest.TestCase):
|
||||
_screen_path=lambda: polygon,
|
||||
)
|
||||
with (
|
||||
patch.object(self.image_gesture, 'drawing_in_region', return_value=True),
|
||||
patch.object(self.image_gesture, "drawing_in_region", return_value=True),
|
||||
patch.object(
|
||||
self.image_gesture,
|
||||
'dashed_line_segments',
|
||||
return_value=('segments',),
|
||||
"dashed_line_segments",
|
||||
return_value=("segments",),
|
||||
) as build_outline,
|
||||
patch.object(
|
||||
self.image_gesture,
|
||||
'preview_fill_triangles',
|
||||
return_value=('triangles',),
|
||||
"preview_fill_triangles",
|
||||
return_value=("triangles",),
|
||||
) as build_fill,
|
||||
patch.object(self.image_gesture, 'draw_polygon_fill') as draw_fill,
|
||||
patch.object(self.image_gesture, 'draw_dashed_line') as draw_outline,
|
||||
patch.object(self.image_gesture, "draw_polygon_fill") as draw_fill,
|
||||
patch.object(self.image_gesture, "draw_dashed_line") as draw_outline,
|
||||
):
|
||||
self.image_gesture.ImageRegionGesture._draw_region(operator)
|
||||
self.image_gesture.ImageRegionGesture._draw_region(operator)
|
||||
|
||||
build_fill.assert_called_once_with(polygon, 'NONZERO')
|
||||
build_fill.assert_called_once_with(polygon, "NONZERO")
|
||||
build_outline.assert_called_once_with((*polygon, polygon[0]))
|
||||
self.assertEqual(draw_fill.call_count, 2)
|
||||
draw_fill.assert_called_with(
|
||||
polygon,
|
||||
('triangles',),
|
||||
("triangles",),
|
||||
(1.0, 1.0, 1.0, 0.16),
|
||||
)
|
||||
self.assertEqual(draw_outline.call_count, 2)
|
||||
draw_outline.assert_called_with((), segments=('segments',))
|
||||
draw_outline.assert_called_with((), segments=("segments",))
|
||||
|
||||
def test_image_lasso_does_not_add_stationary_or_nearly_collinear_points(self):
|
||||
path = [(0.0, 0.0)]
|
||||
@@ -1912,8 +1988,7 @@ class AddonTest(unittest.TestCase):
|
||||
previous_ops = getattr(self.fake_bpy, "ops", None)
|
||||
self.fake_bpy.ops = SimpleNamespace(
|
||||
wm=SimpleNamespace(
|
||||
tool_set_by_id=lambda **options: calls.append(options)
|
||||
or {"FINISHED"}
|
||||
tool_set_by_id=lambda **options: calls.append(options) or {"FINISHED"}
|
||||
)
|
||||
)
|
||||
try:
|
||||
@@ -1935,9 +2010,7 @@ class AddonTest(unittest.TestCase):
|
||||
|
||||
endpoint = self.image_gesture.snapped_line_endpoint((0, 0), (10, 1), True)
|
||||
self.assertAlmostEqual(math.degrees(math.atan2(endpoint[1], endpoint[0])), 5.0)
|
||||
preview = self.image_gesture.line_half_plane_polygon(
|
||||
(0, 5), (10, 5), (10, 10)
|
||||
)
|
||||
preview = self.image_gesture.line_half_plane_polygon((0, 5), (10, 5), (10, 10))
|
||||
image = self.image_gesture.image_line_half_plane_polygon(
|
||||
((0, 5), (10, 5)), (10, 10)
|
||||
)
|
||||
@@ -2191,5 +2264,6 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertEqual(calls[0][1]["text"], "Working")
|
||||
self.assertEqual(calls[1], ("operator", "anyimage.cancel_job"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user