Add tests and implement boundary smoothing and cutout functionality
- Introduced tests for boundary smoothing in `test_boundary_smoothing.py`, verifying that smoothing reduces stair-step artifacts while preserving boundary integrity. - Added tests for cutout boundary behavior in `test_cutout_boundary.py`, ensuring proper handling of boundary triangles and normal blending. - Created `test_temporary_attributes.py` to check the cleanup of temporary attributes across depth modifier branches, ensuring no warnings are raised. - Implemented point culling functionality in `test_validity.py`, confirming that invalid points are removed without affecting adjacent geometry. - Developed boundary smoothing logic in `boundary_smoothing.py`, allowing for depth smoothing while maintaining edge integrity. - Added cutout boundary management in `cutout_boundary.py`, including functions for removing boundary triangles and tapering thickness profiles. - Implemented validity checks in `validity.py` to ensure proper deletion of invalid vertices and associated wire edges.
This commit is contained in:
@@ -631,6 +631,89 @@ class ClipboardImageTest(unittest.TestCase):
|
||||
self.assertEqual(brush.texture_slot.map_mode, "STENCIL")
|
||||
self.assertEqual(brush.stencil_dimension, (256.0, 144.0))
|
||||
|
||||
def test_brush_preview_updates_local_copy_only_in_texture_paint(self):
|
||||
for mode in ("PAINT_TEXTURE", "SCULPT", "PAINT_VERTEX"):
|
||||
with self.subTest(mode=mode):
|
||||
brush = SimpleNamespace(
|
||||
texture=None,
|
||||
texture_slot=SimpleNamespace(texture=None),
|
||||
stencil_dimension=(256.0, 256.0),
|
||||
update_tag=Mock(),
|
||||
)
|
||||
context = SimpleNamespace(mode=mode, brush=object())
|
||||
images = [SimpleNamespace(name=name, size=(80, 40)) for name in ("A", "B")]
|
||||
textures = FakeTextures()
|
||||
with (
|
||||
patch.object(self.actions.bpy, "data", SimpleNamespace(textures=textures)),
|
||||
patch.object(self.actions, "_ensure_editable_brush", return_value=brush),
|
||||
patch.object(self.actions, "_set_brush_preview") as preview,
|
||||
):
|
||||
for image in images:
|
||||
self.actions.set_tool_texture(context, image)
|
||||
self.assertIs(brush.texture.image, images[-1])
|
||||
self.assertEqual(preview.call_count, 2 if mode == "PAINT_TEXTURE" else 0)
|
||||
if mode == "PAINT_TEXTURE":
|
||||
self.assertEqual([call.args[1:] for call in preview.call_args_list],
|
||||
[(brush, image) for image in images])
|
||||
|
||||
def test_brush_preview_cleans_export_on_success_cancel_and_error(self):
|
||||
for outcome in ({"FINISHED"}, {"CANCELLED"}, RuntimeError("preview failed")):
|
||||
with self.subTest(outcome=outcome):
|
||||
brush, image = object(), object()
|
||||
context = SimpleNamespace(scene=object(), temp_override=Mock(return_value=nullcontext()))
|
||||
loader = Mock()
|
||||
if isinstance(outcome, Exception):
|
||||
loader.side_effect = outcome
|
||||
else:
|
||||
loader.return_value = outcome
|
||||
with (
|
||||
patch.object(self.actions, "save_packed_image", return_value="preview.png"),
|
||||
patch.object(self.actions, "cleanup_image_input") as cleanup,
|
||||
patch.object(self.actions.bpy, "ops", SimpleNamespace(
|
||||
ed=SimpleNamespace(lib_id_load_custom_preview=loader)), create=True),
|
||||
):
|
||||
expectation = nullcontext() if outcome == {"FINISHED"} else self.assertRaises(self.actions.BrushPreviewError)
|
||||
with expectation:
|
||||
self.actions._set_brush_preview(context, brush, image)
|
||||
cleanup.assert_called_once_with("preview.png", True)
|
||||
context.temp_override.assert_called_once_with(id=brush)
|
||||
loader.assert_called_once_with(filepath="preview.png")
|
||||
|
||||
def test_preview_failure_preserves_texture_and_redraws(self):
|
||||
textures = FakeTextures()
|
||||
brush = SimpleNamespace(
|
||||
texture=None, texture_slot=SimpleNamespace(texture=None),
|
||||
stencil_dimension=(256.0, 256.0), update_tag=Mock(),
|
||||
)
|
||||
context = SimpleNamespace(mode="PAINT_TEXTURE", brush=brush, area=SimpleNamespace(tag_redraw=Mock()))
|
||||
image = SimpleNamespace(name="Clipboard", size=(80, 40))
|
||||
with (
|
||||
patch.object(self.actions.bpy, "data", SimpleNamespace(textures=textures)),
|
||||
patch.object(self.actions, "_set_brush_preview", side_effect=self.actions.BrushPreviewError("preview failed")),
|
||||
self.assertRaises(self.actions.BrushPreviewError),
|
||||
):
|
||||
self.actions.set_tool_texture(context, image)
|
||||
self.assertEqual(len(textures), 1)
|
||||
self.assertIs(brush.texture.image, image)
|
||||
context.area.tag_redraw.assert_called_once()
|
||||
|
||||
def test_preview_warning_finishes_paste(self):
|
||||
module = self.addon.clipboard_image
|
||||
operator = module.PasteClipboardImage()
|
||||
operator.paste_target = "TOOL_TEXTURE"
|
||||
operator.location_set = False
|
||||
operator.import_as = "REFERENCE"
|
||||
operator.shadeless = False
|
||||
operator.report = Mock()
|
||||
with (
|
||||
patch.object(module, "_should_defer_to_native_paste", return_value=False),
|
||||
patch.object(module, "read_clipboard_image", return_value=(b"image", ".png")),
|
||||
patch.object(module, "acquire_packed_image", return_value=(object(), False)),
|
||||
patch.object(module, "paste_image", side_effect=self.actions.BrushPreviewError("preview failed")),
|
||||
):
|
||||
self.assertEqual(operator.execute(SimpleNamespace()), {"FINISHED"})
|
||||
operator.report.assert_called_once_with({"WARNING"}, "preview failed")
|
||||
|
||||
def test_localizes_read_only_active_brush(self):
|
||||
local_brush = SimpleNamespace(is_editable=True, library=None)
|
||||
linked_brush = SimpleNamespace(
|
||||
|
||||
Reference in New Issue
Block a user