Files
AnyImage/tests/test_addon.py
nan aee460fe7f Update SnapForm to version 0.10.10 and add technical documentation
- Bump SnapForm version in uv.lock from 0.9.30 to 0.10.10.
- Introduce comprehensive technical documentation for SnapForm, detailing features, usage instructions, and development guidelines.
2026-08-08 19:05:42 +08:00

2046 lines
73 KiB
Python

import importlib
import sys
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
from types import ModuleType, SimpleNamespace
from unittest.mock import patch
PROJECT_ROOT = Path(__file__).resolve().parents[1]
SOURCE_ROOT = PROJECT_ROOT / "src"
class AddonTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.remove_modules()
fake_bpy = ModuleType("bpy")
fake_bpy.props = SimpleNamespace(
BoolProperty=lambda **options: options,
EnumProperty=lambda **options: options,
FloatProperty=lambda **options: options,
IntProperty=lambda **options: options,
PointerProperty=lambda **options: options,
StringProperty=lambda **options: options,
)
fake_bpy.types = SimpleNamespace(
AddonPreferences=object,
Operator=object,
Panel=object,
PropertyGroup=object,
Scene=type("Scene", (), {}),
STATUSBAR_HT_header=SimpleNamespace(
append=lambda draw: cls.status_bar_draws.append(draw),
remove=lambda draw: cls.status_bar_draws.remove(draw),
),
VIEW3D_MT_object_context_menu=SimpleNamespace(
append=lambda draw: cls.context_menu_draws.append(draw),
remove=lambda draw: cls.context_menu_draws.remove(draw),
),
)
cls.registered = []
cls.status_bar_draws = []
cls.context_menu_draws = []
fake_bpy.utils = SimpleNamespace(
register_class=cls.registered.append,
unregister_class=cls.registered.remove,
)
fake_bpy.context = SimpleNamespace(
preferences=SimpleNamespace(addons={}),
)
fake_bpy.app = SimpleNamespace(online_access=True)
fake_bpy.path = SimpleNamespace(abspath=lambda path: path)
fake_bpy.data = SimpleNamespace(
objects=SimpleNamespace(get=lambda _name: None),
)
sys.modules["bpy"] = fake_bpy
sys.path.insert(0, str(SOURCE_ROOT))
cls.addon = importlib.import_module("snapform")
cls.fake_bpy = fake_bpy
@classmethod
def tearDownClass(cls):
sys.path.remove(str(SOURCE_ROOT))
sys.modules.pop("bpy", None)
cls.remove_modules()
@staticmethod
def remove_modules():
for module_name in tuple(sys.modules):
if module_name == "snapform" or module_name.startswith("snapform."):
sys.modules.pop(module_name)
def tearDown(self):
self.registered.clear()
self.status_bar_draws.clear()
self.context_menu_draws.clear()
self.addon.operators._active_job = None
self.addon.image_depth._last_depth_plane_model = "BASE"
self.fake_bpy.data = SimpleNamespace(
objects=SimpleNamespace(get=lambda _name: None),
)
if hasattr(self.fake_bpy.types.Scene, "snapform_settings"):
del self.fake_bpy.types.Scene.snapform_settings
def test_uses_extension_manifest_instead_of_legacy_bl_info(self):
self.assertFalse(hasattr(self.addon, "bl_info"))
def test_registers_and_unregisters_extension_types(self):
self.addon.register()
self.assertEqual(self.registered, list(self.addon.CLASSES))
self.assertTrue(
hasattr(self.fake_bpy.types.Scene, "snapform_settings")
)
self.assertEqual(
self.status_bar_draws,
[self.addon.draw_status_bar],
)
self.assertEqual(
self.context_menu_draws,
[self.addon.draw_image_context_menu],
)
self.addon.unregister()
self.assertEqual(self.registered, [])
self.assertFalse(
hasattr(self.fake_bpy.types.Scene, "snapform_settings")
)
self.assertEqual(self.status_bar_draws, [])
self.assertEqual(self.context_menu_draws, [])
def test_registers_depth_plane_operator_without_cache_rebuild(self):
self.assertIn(
self.addon.ConvertImageToDepthPlane,
self.addon.CLASSES,
)
self.assertIn(self.addon.BuildDepthPlane, self.addon.CLASSES)
self.assertEqual(
self.addon.BuildDepthPlane.bl_options,
{"INTERNAL"},
)
self.assertFalse(hasattr(self.addon, "ImportDepthPlane"))
def test_registers_image_background_operators(self):
self.assertIn(self.addon.RemoveImageBackground, self.addon.CLASSES)
self.assertIn(self.addon.ApplyRemovedBackground, self.addon.CLASSES)
self.assertIn(self.addon.BuildOriginalDepthPlane, self.addon.CLASSES)
self.assertEqual(
self.addon.ApplyRemovedBackground.bl_options,
{"INTERNAL"},
)
def test_background_result_creates_new_packed_image(self):
original_image = SimpleNamespace(
name="img.png",
use_fake_user=False,
)
transform = object()
source_object = SimpleNamespace(
data=original_image,
matrix_world=transform,
empty_display_size=2.5,
empty_image_offset=(-0.25, -0.75),
)
pack_calls = []
result_image = SimpleNamespace(
name="foreground.png",
pack=lambda: pack_calls.append(True),
)
load_calls = []
remove_calls = []
self.fake_bpy.data = SimpleNamespace(
images=SimpleNamespace(
load=lambda *args, **options: load_calls.append(
(args, options)
)
or result_image,
remove=remove_calls.append,
),
)
with TemporaryDirectory() as directory:
output_path = Path(directory) / "foreground.png"
output_path.touch()
created = self.addon.image_depth.create_removed_background_image(
source_object,
output_path,
)
self.assertIs(created, result_image)
self.assertIs(source_object.data, result_image)
self.assertIsNot(source_object.data, original_image)
self.assertTrue(original_image.use_fake_user)
self.assertIs(source_object.matrix_world, transform)
self.assertEqual(source_object.empty_display_size, 2.5)
self.assertEqual(
source_object.empty_image_offset,
(-0.25, -0.75),
)
self.assertEqual(result_image.name, "img.foreground")
self.assertEqual(pack_calls, [True])
self.assertEqual(remove_calls, [])
self.assertEqual(
load_calls[0][1],
{"check_existing": False},
)
def test_background_result_name_handles_numbered_format_suffix(self):
source_object = SimpleNamespace(
data=SimpleNamespace(name="img.png.001"),
)
self.assertEqual(
self.addon.image_depth.removed_background_data_name(
source_object
),
"img.001.foreground",
)
def test_depth_background_image_does_not_replace_source_empty(self):
original_image = SimpleNamespace(
name="img.png",
use_fake_user=False,
)
source_object = SimpleNamespace(data=original_image)
pack_calls = []
result_image = SimpleNamespace(
name="foreground.png",
pack=lambda: pack_calls.append(True),
)
self.fake_bpy.data = SimpleNamespace(
images=SimpleNamespace(
load=lambda *_args, **_options: result_image,
remove=lambda _image: None,
),
)
with TemporaryDirectory() as directory:
output_path = Path(directory) / "foreground.png"
output_path.touch()
created = self.addon.image_depth.create_removed_background_image(
source_object,
output_path,
assign_to_source=False,
)
self.assertIs(created, result_image)
self.assertIs(source_object.data, original_image)
self.assertFalse(original_image.use_fake_user)
self.assertEqual(result_image.name, "img.foreground")
self.assertEqual(pack_calls, [True])
def test_sequence_background_result_preserves_empty_image_start(self):
original_image = SimpleNamespace(
name="img.png",
use_fake_user=False,
)
image_user = SimpleNamespace(
frame_start=4,
frame_offset=0,
frame_duration=1,
use_auto_refresh=False,
use_cyclic=False,
)
source_object = SimpleNamespace(
data=original_image,
image_user=image_user,
)
result_image = SimpleNamespace(
name="foreground.png",
source="SEQUENCE",
pack=lambda: None,
unpack=lambda **_options: None,
packed_files=(),
)
self.fake_bpy.data = SimpleNamespace(
images=SimpleNamespace(
load=lambda *_args, **_options: result_image,
remove=lambda _image: None,
),
)
with TemporaryDirectory() as directory:
result_directory = Path(directory)
(result_directory / "foreground.0001.png").touch()
(result_directory / "foreground.0002.png").touch()
created = self.addon.image_depth.create_removed_background_image(
source_object,
result_directory,
)
self.assertIs(created, result_image)
self.assertIs(source_object.data, result_image)
self.assertEqual(image_user.frame_start, 4)
self.assertEqual(image_user.frame_duration, 2)
self.assertEqual(image_user.frame_offset, 0)
self.assertTrue(image_user.use_auto_refresh)
self.assertFalse(image_user.use_cyclic)
def test_image_empty_background_action_starts_production_job(self):
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(name="img.png"),
)
context = SimpleNamespace(
object=source_object,
scene=SimpleNamespace(
snapform_settings=SimpleNamespace(video_frames=16)
),
)
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
run_job=lambda **options: calls.append(options)
or {"RUNNING_MODAL"},
),
)
operator = self.addon.RemoveImageBackground()
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
with patch.object(
self.addon.image_depth,
"prepare_image_input",
return_value=(input_path, True),
):
result = operator.execute(context)
self.assertEqual(result, {"FINISHED"})
self.assertEqual(calls[0]["job_type"], "IMAGE_BACKGROUND")
self.assertEqual(calls[0]["snapform_input_path"], str(input_path))
self.assertEqual(
calls[0]["snapform_source_object_name"],
"Reference",
)
self.assertTrue(calls[0]["snapform_delete_input"])
def test_original_depth_background_starts_independent_job(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ORIGINAL"
operator.snapform_remove_background = True
operator.snapform_displacement_via = "MODIFIER"
operator.snapform_unshaded = False
operator.snapform_subdivision_level = 5
operator.snapform_delete_original = False
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(name="img.png"),
)
context = SimpleNamespace(object=source_object, scene=object())
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
run_job=lambda **options: calls.append(options)
or {"RUNNING_MODAL"},
),
)
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
with patch.object(
self.addon.image_depth,
"prepare_image_input",
return_value=(input_path, False),
):
result = operator.execute(context)
self.assertEqual(result, {"FINISHED"})
self.assertEqual(calls[0]["job_type"], "ORIGINAL_BACKGROUND")
self.assertFalse(calls[0]["snapform_delete_source"])
def test_original_depth_animation_extracts_frames_without_background(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ORIGINAL"
operator.snapform_remove_background = False
operator.snapform_displacement_via = "MODIFIER"
operator.snapform_unshaded = False
operator.snapform_subdivision_level = 5
operator.snapform_delete_original = False
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(
name="clip.mp4",
source="MOVIE",
frame_duration=97,
),
)
scene = SimpleNamespace(
snapform_settings=SimpleNamespace(video_frames=16)
)
context = SimpleNamespace(object=source_object, scene=scene)
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
run_job=lambda **options: calls.append(options)
or {"RUNNING_MODAL"},
),
)
with TemporaryDirectory() as directory:
input_path = Path(directory) / "clip.mp4"
input_path.touch()
with patch.object(
self.addon.image_depth,
"prepare_image_input",
return_value=(input_path, False),
):
result = operator.execute(context)
self.assertEqual(result, {"FINISHED"})
self.assertEqual(scene.snapform_settings.video_frames, 97)
self.assertEqual(calls[0]["job_type"], "ORIGINAL_FRAMES")
self.assertEqual(calls[0]["snapform_input_path"], str(input_path))
self.assertEqual(calls[0]["snapform_source_object_name"], "Reference")
self.assertFalse(calls[0]["snapform_delete_input"])
self.assertFalse(calls[0]["snapform_delete_source"])
def test_conversion_dialog_exposes_requested_settings(self):
operator = self.addon.ConvertImageToDepthPlane
self.assertNotIn(
"default",
operator.__annotations__["snapform_model"],
)
self.assertTrue(operator.__annotations__["snapform_use_ray_pose"]["default"])
self.assertEqual(
operator.__annotations__["snapform_process_res_factor"]["default"],
1.0,
)
self.assertTrue(
operator.__annotations__["snapform_delete_original"]["default"]
)
self.assertFalse(
operator.__annotations__["snapform_unshaded"]["default"]
)
self.assertFalse(
operator.__annotations__["snapform_remove_background"]["default"]
)
self.assertEqual(
operator.__annotations__["snapform_subdivision_level"]["default"],
5,
)
self.assertEqual(
set(operator.__annotations__),
{
"snapform_depth_source",
"snapform_displacement_via",
"snapform_unshaded",
"snapform_remove_background",
"snapform_model",
"snapform_use_ray_pose",
"snapform_process_res_factor",
"snapform_delete_original",
"snapform_subdivision_level",
},
)
depth_sources = operator.__annotations__["snapform_depth_source"]
self.assertEqual(depth_sources["default"], "ORIGINAL")
self.assertEqual(
[item[0] for item in depth_sources["items"]],
["ESTIMATE", "ORIGINAL"],
)
self.assertEqual(depth_sources["items"][0][1], "AI Generated")
self.assertEqual(depth_sources["name"], "Depth from")
displacement_via = operator.__annotations__[
"snapform_displacement_via"
]
self.assertEqual(displacement_via["default"], "MODIFIER")
self.assertEqual(displacement_via["name"], "Displace via")
self.assertEqual(
[item[0] for item in displacement_via["items"]],
["MODIFIER", "MATERIAL"],
)
self.assertEqual(
operator.__annotations__["snapform_unshaded"]["name"],
"Unshaded",
)
model_items_callback = operator.__annotations__["snapform_model"]["items"]
with patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
):
model_items = model_items_callback(None, None)
self.assertEqual(
[item[0] for item in model_items],
[
"SMALL",
"BASE",
"LARGE_1_1",
"GIANT_1_1",
"NESTED_GIANT_LARGE_1_1",
],
)
def test_conversion_dialog_remembers_last_depth_model(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.properties = SimpleNamespace(
is_property_set=lambda _name: False,
)
context = SimpleNamespace(
object=SimpleNamespace(
data=SimpleNamespace(source="FILE", frame_duration=1)
),
scene=SimpleNamespace(
snapform_settings=SimpleNamespace(video_frames=16)
),
window_manager=SimpleNamespace(
invoke_props_dialog=lambda invoked, **_options: invoked,
),
)
self.addon.image_depth._last_depth_plane_model = "GIANT_1_1"
with patch.object(
self.addon.runtime,
"model_ready",
side_effect=lambda key: key == "GIANT_1_1",
):
result = operator.invoke(context, None)
self.assertIs(result, operator)
self.assertEqual(operator.snapform_model, "GIANT_1_1")
def test_remove_background_defaults_to_empty_image_frames(self):
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(source="MOVIE", frame_duration=97),
image_user=SimpleNamespace(frame_duration=24),
)
scene = SimpleNamespace(
snapform_settings=SimpleNamespace(video_frames=16)
)
context = SimpleNamespace(object=source_object, scene=scene)
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
run_job=lambda **options: calls.append(options)
or {"RUNNING_MODAL"},
),
)
operator = self.addon.RemoveImageBackground()
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
with patch.object(
self.addon.image_depth,
"prepare_image_input",
return_value=(input_path, False),
):
result = operator.execute(context)
self.assertEqual(result, {"FINISHED"})
self.assertEqual(scene.snapform_settings.video_frames, 24)
def test_conversion_execute_uses_remove_background_frame_range(self):
source_object = SimpleNamespace(
name="Reference",
data=SimpleNamespace(
name="clip.mp4",
source="MOVIE",
frame_duration=97,
size=(1920, 1080),
),
image_user=SimpleNamespace(frame_duration=24),
)
scene = SimpleNamespace(
snapform_settings=SimpleNamespace(video_frames=16)
)
context = SimpleNamespace(object=source_object, scene=scene)
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ESTIMATE"
operator.snapform_model = "BASE"
operator.snapform_use_ray_pose = True
operator.snapform_process_res_factor = 1.0
operator.snapform_displacement_via = "MODIFIER"
operator.snapform_unshaded = False
operator.snapform_remove_background = False
operator.snapform_subdivision_level = 5
operator.snapform_delete_original = False
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
run_job=lambda **options: calls.append(options)
or {"RUNNING_MODAL"},
),
)
with TemporaryDirectory() as directory:
input_path = Path(directory) / "clip.mp4"
input_path.touch()
with patch.object(
self.addon.image_depth,
"prepare_image_input",
return_value=(input_path, False),
):
result = operator.execute(context)
self.assertEqual(result, {"FINISHED"})
self.assertEqual(scene.snapform_settings.video_frames, 24)
self.assertEqual(calls[0]["job_type"], "IMAGE_DEPTH")
def test_conversion_dialog_only_lists_installed_models(self):
items_callback = self.addon.ConvertImageToDepthPlane.__annotations__[
"snapform_model"
]["items"]
with patch.object(
self.addon.runtime,
"model_ready",
side_effect=lambda key: key in {"SMALL", "BASE"},
):
items = items_callback(None, None)
self.assertEqual([item[0] for item in items], ["SMALL", "BASE"])
def test_update_status_formats_stage_progress(self):
operator = self.addon.RunJob()
self.assertEqual(
operator._format_status(
{
"progress": 0.5,
"message": "2 downloaded",
"stage": 3,
"stages": 8,
"stage_label": "pytorch",
},
"fallback",
),
"3/8 pytorch: 2 downloaded",
)
self.assertEqual(
operator._format_status(
{
"progress": 0.0,
"message": "",
"stage": 6,
"stages": 8,
"stage_label": "da3 small",
},
"fallback",
),
"6/8 da3 small",
)
self.assertEqual(
operator._format_status(
{
"progress": 0.5,
"message": "Estimating depth",
},
"fallback",
),
"Estimating depth",
)
def test_model_options_mark_only_models_that_are_not_downloaded(self):
items_callback = self.addon.SnapFormSettings.__annotations__["model"][
"items"
]
with patch.object(
self.addon.runtime,
"model_ready",
side_effect=lambda key: key == "BASE",
):
items = items_callback(None, None)
labels = {item[0]: item[1] for item in items}
self.assertEqual(labels["BASE"], "DA3 Base")
self.assertEqual(labels["SMALL"], "DA3 Small (Missing)")
def test_packed_image_is_exported_without_unpacking(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ESTIMATE"
operator.snapform_model = "GIANT_1_1"
operator.snapform_use_ray_pose = True
operator.snapform_process_res_factor = 0.5
operator.snapform_displacement_via = "MATERIAL"
operator.snapform_unshaded = True
operator.snapform_remove_background = True
operator.snapform_subdivision_level = 5
operator.snapform_delete_original = True
image = SimpleNamespace(
name="img.png",
filepath="",
packed_file=object(),
size=(1920, 1080),
)
context = SimpleNamespace(
object=SimpleNamespace(name="Reference", data=image),
scene=object(),
)
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
run_job=lambda **options: calls.append(options)
or {"RUNNING_MODAL"},
),
)
with TemporaryDirectory() as directory:
packed_path = Path(directory) / "packed.png"
packed_path.touch()
with patch.object(
self.addon.image_depth,
"save_packed_image",
return_value=packed_path,
) as save_packed_image, patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
):
result = operator.execute(context)
self.assertEqual(result, {"FINISHED"})
save_packed_image.assert_called_once_with(image, context.scene)
self.assertEqual(calls[0]["snapform_input_path"], str(packed_path))
self.assertEqual(calls[0]["snapform_process_res"], 960)
self.assertEqual(calls[0]["snapform_model_key"], "GIANT_1_1")
self.assertEqual(
calls[0]["snapform_displacement_via"],
"MATERIAL",
)
self.assertTrue(calls[0]["snapform_unshaded"])
self.assertTrue(calls[0]["snapform_remove_background"])
self.assertEqual(calls[0]["snapform_depth_data_name"], "img.depth")
self.assertTrue(calls[0]["snapform_delete_input"])
self.assertEqual(
self.addon.image_depth._last_depth_plane_model,
"GIANT_1_1",
)
def test_packed_image_export_writes_a_png_copy(self):
class PackedImage:
def save_render(self, filepath, scene):
self.filepath = filepath
self.scene = scene
self.file_format = scene.render.image_settings.file_format
Path(filepath).write_bytes(b"packed image")
image = PackedImage()
image_settings = SimpleNamespace(file_format="OPEN_EXR")
scene = SimpleNamespace(
render=SimpleNamespace(image_settings=image_settings),
)
with TemporaryDirectory() as directory:
output_path = Path(directory) / "input.png"
with patch.object(
self.addon.runtime,
"temporary_input_path",
return_value=output_path,
):
result = self.addon.image_depth.save_packed_image(image, scene)
self.assertEqual(result, output_path)
self.assertEqual(output_path.read_bytes(), b"packed image")
self.assertEqual(image.filepath, str(output_path))
self.assertIs(image.scene, scene)
self.assertEqual(image.file_format, "PNG")
self.assertEqual(image_settings.file_format, "OPEN_EXR")
def test_temporary_packed_input_is_removed_when_job_closes(self):
closed = []
with TemporaryDirectory() as directory:
input_path = Path(directory) / "packed.png"
input_path.touch()
self.addon.operators._active_job = SimpleNamespace(
log_file=SimpleNamespace(close=lambda: closed.append(True)),
temporary_input_path=input_path,
)
self.addon.operators.close_active_job()
self.assertFalse(input_path.exists())
self.assertEqual(closed, [True])
self.assertIsNone(self.addon.operators.active_job())
def test_temporary_frame_directory_is_removed_when_job_closes(self):
closed = []
with TemporaryDirectory() as directory:
root = Path(directory)
temporary_directory = root / "inputs" / "frames"
temporary_directory.mkdir(parents=True)
(temporary_directory / "frame-0000.jpg").touch()
self.addon.operators._active_job = SimpleNamespace(
log_file=SimpleNamespace(close=lambda: closed.append(True)),
temporary_input_path=temporary_directory,
)
self.addon.operators.close_active_job()
self.assertFalse(temporary_directory.exists())
self.assertEqual(closed, [True])
self.assertIsNone(self.addon.operators.active_job())
def test_prepare_image_input_passes_movie_file_through(self):
with TemporaryDirectory() as directory:
video_path = Path(directory) / "clip.mp4"
video_path.touch()
image = SimpleNamespace(
source="MOVIE",
filepath=str(video_path),
)
result, delete_input = self.addon.image_depth.prepare_image_input(
image,
object(),
)
self.assertEqual(result, video_path)
self.assertFalse(delete_input)
def test_prepare_image_input_copies_sequence_frames(self):
with TemporaryDirectory() as directory:
root = Path(directory)
sequence_directory = root / "seq"
sequence_directory.mkdir()
(sequence_directory / "frame-0001.jpg").touch()
(sequence_directory / "frame-0002.jpg").touch()
image = SimpleNamespace(
source="SEQUENCE",
filepath=str(sequence_directory / "frame-0001.jpg"),
)
temporary_directory = root / "temp-frames"
temporary_directory.mkdir()
with patch.object(
self.addon.runtime,
"temporary_input_directory",
return_value=temporary_directory,
):
result, delete_input = (
self.addon.image_depth.prepare_image_input(
image,
object(),
)
)
self.assertEqual(result, temporary_directory)
self.assertTrue(delete_input)
self.assertTrue((temporary_directory / "frame.0001.jpg").is_file())
self.assertTrue((temporary_directory / "frame.0002.jpg").is_file())
def test_image_depth_model_and_ray_pose_are_independent(self):
settings = SimpleNamespace(
input_path="debug-input.png",
export_format="gs_ply",
model="BASE",
gs_model="GIANT_1_1",
)
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.IMAGE_DEPTH_JOB
operator.snapform_source_object_name = "Reference"
operator.snapform_process_res = 336
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
operator.snapform_input_path = str(input_path)
paths = {
"log": Path(directory) / "job.log",
"status": Path(directory) / "job.json",
"result": Path(directory) / "result",
}
for snapform_model, snapform_ray_pose in (
("SMALL", True),
("BASE", False),
("LARGE_1_1", True),
("GIANT_1_1", False),
("NESTED_GIANT_LARGE_1_1", True),
):
with self.subTest(model=snapform_model), patch.object(
self.addon.runtime,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.runtime,
"runtime_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
) as model_ready, patch.object(
self.addon.runtime,
"reconstruct_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as reconstruct:
operator.snapform_model_key = snapform_model
operator.snapform_use_ray_pose = snapform_ray_pose
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(
command,
self.addon.runtime.HostJobRequest,
)
self.assertEqual(prepared_paths, paths)
model_ready.assert_called_once_with(snapform_model)
self.assertEqual(reconstruct.call_args.args[1], "mini_npz")
self.assertEqual(
reconstruct.call_args.kwargs["input_path"],
input_path,
)
self.assertEqual(
reconstruct.call_args.kwargs["model_key"],
snapform_model,
)
self.assertEqual(
reconstruct.call_args.kwargs["process_res"],
336,
)
self.assertEqual(
reconstruct.call_args.kwargs["process_res_method"],
"upper_bound_resize",
)
self.assertEqual(
reconstruct.call_args.kwargs["use_ray_pose"],
snapform_ray_pose,
)
def test_image_depth_background_option_requires_ben2(self):
settings = SimpleNamespace(
input_path="debug-input.png",
export_format="mini_npz",
model="BASE",
gs_model="GIANT_1_1",
)
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.IMAGE_DEPTH_JOB
operator.snapform_model_key = "BASE"
operator.snapform_use_ray_pose = True
operator.snapform_process_res = 1024
operator.snapform_remove_background = True
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
operator.snapform_input_path = str(input_path)
with patch.object(
self.addon.runtime,
"new_job_paths",
return_value={
"log": Path(directory) / "job.log",
"status": Path(directory) / "job.json",
"result": Path(directory) / "result",
},
), patch.object(
self.addon.runtime,
"runtime_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"model_ready",
side_effect=lambda key: key == "BASE",
):
with self.assertRaisesRegex(RuntimeError, "BEN2 Base"):
operator._prepare_command(None, settings)
def test_image_depth_background_option_reaches_host_request(self):
runtime = importlib.import_module("snapform.runtime")
settings = SimpleNamespace(
input_path="input.png",
model="BASE",
gs_model="GIANT_1_1",
process_res=504,
process_res_method="upper_bound_resize",
conf_thresh_percentile=40.0,
num_max_points=1_000_000,
show_cameras=True,
gs_views_interval=1,
video_frames=16,
device="AUTO",
use_ray_pose=False,
ref_view_strategy="saddle_balanced",
)
with patch.object(
runtime,
"runtime_python",
return_value=Path("python"),
), patch.object(
runtime,
"model_directory",
side_effect=lambda key: Path(key.lower()),
):
request = runtime.reconstruct_request(
settings,
"mini_npz",
Path("status.json"),
Path("result"),
remove_background=True,
)
self.assertEqual(
request.payload["parameters"]["remove_background_model_dir"],
str(Path("ben2_base")),
)
def test_preferences_use_package_identifier(self):
self.assertEqual(
self.addon.SnapFormPreferences.bl_idname,
"snapform",
)
def test_default_storage_is_shared_in_user_home(self):
runtime = importlib.import_module("snapform.runtime")
self.assertEqual(runtime.DEFAULT_STORAGE_ROOT.name, ".snapform")
self.assertEqual(
self.addon.SnapFormPreferences.__annotations__["storage_root"][
"default"
],
str(runtime.DEFAULT_STORAGE_ROOT),
)
def test_ai_host_uses_loopback_address(self):
runtime = importlib.import_module("snapform.runtime")
self.assertEqual(runtime.HOST_ADDRESS, "127.0.0.1")
def test_closing_blender_terminates_host_it_started(self):
runtime = importlib.import_module("snapform.runtime")
previous_connection = runtime._host_connection
previous_registered = runtime._registered_instance_id
runtime._host_connection = SimpleNamespace(port=12345)
runtime._registered_instance_id = "instance"
try:
with patch.object(
runtime,
"_host_http_request",
) as request, patch.object(
runtime,
"_close_launched_host",
) as close_host:
runtime.detach_host_client()
request.assert_called_once()
self.assertEqual(request.call_args.args[1], "DELETE")
close_host.assert_called_once_with(terminate=True)
finally:
runtime._host_connection = previous_connection
runtime._registered_instance_id = previous_registered
self.assertIsNone(runtime._host_connection)
self.assertIsNone(runtime._registered_instance_id)
def test_process_res_method_defaults_to_upper_bound_resize(self):
process_res = self.addon.SnapFormSettings.__annotations__[
"process_res"
]
process_res_method = (
self.addon.SnapFormSettings.__annotations__[
"process_res_method"
]
)
self.assertEqual(process_res["default"], 504)
self.assertEqual(process_res_method["default"], "upper_bound_resize")
def test_reconstruct_request_preserves_da3_parameter_names(self):
runtime = importlib.import_module("snapform.runtime")
settings = SimpleNamespace(
input_path="input.png",
model="GIANT_1_1",
gs_model="NESTED_GIANT_LARGE_1_1",
process_res=504,
process_res_method="upper_bound_resize",
conf_thresh_percentile=40.0,
num_max_points=1_000_000,
show_cameras=True,
gs_views_interval=1,
video_frames=16,
device="CUDA",
use_ray_pose=False,
ref_view_strategy="saddle_balanced",
)
with patch.object(
runtime,
"runtime_python",
return_value=Path("python"),
), patch.object(
runtime,
"model_directory",
return_value=Path("model"),
):
request = runtime.reconstruct_request(
settings,
"gs_ply",
Path("status.json"),
Path("result"),
)
parameters = request.payload["parameters"]
self.assertEqual(request.payload["command"], "reconstruct")
self.assertEqual(parameters["export_format"], "gs_ply")
for parameter in (
"process_res",
"process_res_method",
"conf_thresh_percentile",
"num_max_points",
"show_cameras",
"gs_views_interval",
"use_ray_pose",
"ref_view_strategy",
):
with self.subTest(parameter=parameter):
self.assertIn(parameter, parameters)
def test_remove_background_request_passes_video_frames(self):
runtime = importlib.import_module("snapform.runtime")
settings = SimpleNamespace(device="AUTO", video_frames=24)
with patch.object(
runtime,
"runtime_python",
return_value=Path("python"),
), patch.object(
runtime,
"model_directory",
return_value=Path("ben2_base"),
):
request = runtime.remove_background_request(
settings,
Path("clip.mp4"),
Path("status.json"),
Path("result"),
"BEN2_BASE",
start_frame=70,
)
parameters = request.payload["parameters"]
self.assertEqual(parameters["video_frames"], 24)
self.assertEqual(parameters["start_frame"], 70)
def test_extract_frames_request_passes_frame_range(self):
runtime = importlib.import_module("snapform.runtime")
settings = SimpleNamespace(device="AUTO", video_frames=24)
request = runtime.extract_frames_request(
settings,
Path("clip.mp4"),
Path("status.json"),
Path("result"),
start_frame=70,
)
parameters = request.payload["parameters"]
self.assertEqual(request.payload["command"], "extract-frames")
self.assertEqual(parameters["input"], str(Path("clip.mp4")))
self.assertEqual(parameters["output_dir"], str(Path("result")))
self.assertEqual(parameters["video_frames"], 24)
self.assertEqual(parameters["start_frame"], 70)
def test_new_job_paths_wraps_each_job_in_a_folder(self):
runtime = importlib.import_module("snapform.runtime")
with TemporaryDirectory() as directory:
jobs_root = Path(directory)
with patch.object(
runtime,
"jobs_directory",
return_value=jobs_root,
):
paths = runtime.new_job_paths("RECONSTRUCT")
self.assertTrue(paths["result"].is_dir())
self.assertEqual(paths["result"].parent, jobs_root)
self.assertEqual(paths["log"].parent, paths["result"])
self.assertEqual(paths["status"].parent, paths["result"])
self.assertEqual(paths["status"].stem, paths["result"].name)
def test_new_job_paths_uses_type_time_folder_name(self):
runtime = importlib.import_module("snapform.runtime")
with TemporaryDirectory() as directory:
jobs_root = Path(directory)
with patch.object(
runtime,
"jobs_directory",
return_value=jobs_root,
):
depth_paths = runtime.new_job_paths(
self.addon.operators.IMAGE_DEPTH_JOB
)
background_paths = runtime.new_job_paths(
self.addon.operators.IMAGE_BACKGROUND_JOB
)
frames_paths = runtime.new_job_paths(
self.addon.operators.ORIGINAL_FRAMES_JOB
)
self.assertRegex(
depth_paths["result"].name,
r"^depth-plane_\d{8}-\d{6}$",
)
self.assertTrue(
background_paths["result"].name.startswith(
"remove-background_"
)
)
self.assertTrue(
frames_paths["result"].name.startswith("depth-plane_")
)
def test_empty_image_start_frame_honors_image_user(self):
operators = self.addon.operators
self.assertEqual(
operators.empty_image_start_frame(
SimpleNamespace(
data=SimpleNamespace(source="MOVIE", frame_duration=97),
image_user=SimpleNamespace(
frame_start=1,
frame_offset=70,
frame_duration=24,
)
)
),
71,
)
self.assertEqual(
operators.empty_image_start_frame(
SimpleNamespace(
data=SimpleNamespace(source="MOVIE", frame_duration=97),
image_user=SimpleNamespace(
frame_start=10,
frame_offset=5,
frame_duration=24,
)
)
),
6,
)
self.assertEqual(
operators.empty_image_start_frame(
SimpleNamespace(image_user=None)
),
1,
)
def test_image_depth_job_passes_empty_frame_offset(self):
settings = SimpleNamespace(
input_path="debug-input.png",
export_format="mini_npz",
model="BASE",
gs_model="GIANT_1_1",
process_res=504,
process_res_method="upper_bound_resize",
conf_thresh_percentile=40.0,
num_max_points=1_000_000,
show_cameras=True,
gs_views_interval=1,
video_frames=16,
device="AUTO",
use_ray_pose=False,
ref_view_strategy="saddle_balanced",
)
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.IMAGE_DEPTH_JOB
operator.snapform_source_object_name = "Reference"
operator.snapform_model_key = "BASE"
operator.snapform_use_ray_pose = True
operator.snapform_process_res = 1024
original_get = self.fake_bpy.data.objects.get
self.fake_bpy.data.objects.get = lambda _name: SimpleNamespace(
data=SimpleNamespace(source="MOVIE", frame_duration=97),
image_user=SimpleNamespace(
frame_start=1,
frame_offset=70,
frame_duration=24,
),
)
try:
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
operator.snapform_input_path = str(input_path)
paths = {
"log": Path(directory) / "job.log",
"status": Path(directory) / "job.json",
"result": Path(directory) / "result",
}
with patch.object(
self.addon.runtime,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.runtime,
"runtime_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"reconstruct_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as reconstruct:
operator._prepare_command(None, settings)
finally:
self.fake_bpy.data.objects.get = original_get
self.assertEqual(reconstruct.call_args.kwargs["start_frame"], 71)
def test_reconstruct_request_accepts_image_depth_overrides(self):
runtime = importlib.import_module("snapform.runtime")
settings = SimpleNamespace(
input_path="input.png",
model="BASE",
gs_model="GIANT_1_1",
process_res=504,
process_res_method="upper_bound_resize",
conf_thresh_percentile=40.0,
num_max_points=1_000_000,
show_cameras=True,
gs_views_interval=1,
video_frames=16,
device="AUTO",
use_ray_pose=False,
ref_view_strategy="saddle_balanced",
)
with patch.object(
runtime,
"runtime_python",
return_value=Path("python"),
), patch.object(
runtime,
"model_directory",
return_value=Path("large-model"),
) as snapform_model_directory:
request = runtime.reconstruct_request(
settings,
"mini_npz",
Path("status.json"),
Path("result"),
model_key="BASE",
process_res=252,
process_res_method="lower_bound_resize",
use_ray_pose=False,
)
snapform_model_directory.assert_called_once_with("BASE")
parameters = request.payload["parameters"]
self.assertEqual(parameters["process_res"], 252)
self.assertEqual(
parameters["process_res_method"],
"lower_bound_resize",
)
self.assertEqual(parameters["use_ray_pose"], 0)
def test_export_format_property_includes_all_da3_formats(self):
export_format = self.addon.SnapFormSettings.__annotations__[
"export_format"
]
identifiers = [item[0] for item in export_format["items"]]
self.assertEqual(
identifiers,
["glb", "gs_ply", "depth_vis", "mini_npz"],
)
self.assertEqual(export_format["default"], "glb")
def test_gs_model_property_only_includes_gaussian_models(self):
gs_model = self.addon.SnapFormSettings.__annotations__[
"gs_model"
]
identifiers = [item[0] for item in gs_model["items"](None, None)]
self.assertEqual(
identifiers,
["GIANT_1_1", "NESTED_GIANT_LARGE_1_1"],
)
def test_debug_panel_is_closed_by_default(self):
self.assertIn(
"DEFAULT_CLOSED",
self.addon.DebugPanel.bl_options,
)
for panel in (self.addon.Da3DebugPanel, self.addon.Ben2DebugPanel):
self.assertEqual(panel.bl_parent_id, self.addon.DebugPanel.bl_idname)
self.assertIn("DEFAULT_CLOSED", panel.bl_options)
def test_host_panel_exposes_shared_host_state_and_controls(self):
self.assertIn(self.addon.HostPanel, self.addon.CLASSES)
self.assertIn(self.addon.ControlHost, self.addon.CLASSES)
self.assertEqual(self.addon.HostPanel.bl_label, "Host")
def test_ai_operation_restarts_a_manually_stopped_host(self):
runtime = self.addon.runtime
connection = runtime.HostConnection(51234, "token", 42, "host")
health = {
"host_schema": runtime.HOST_SCHEMA,
"instance_id": "host",
"busy": False,
"clients": 1,
"cached_models": {},
}
previous_auto_start = runtime._host_auto_start
previous_connection = runtime._host_connection
previous_snapshot = runtime._host_snapshot
runtime._host_auto_start = False
runtime._host_connection = None
runtime._host_snapshot = {"state": "STOPPED", "message": "Stopped"}
try:
with patch.object(
runtime,
"_discover_live_host",
return_value=(connection, health),
), patch.object(
runtime,
"_register_host_client",
), patch.object(
runtime,
"_check_host",
return_value=health,
), patch.object(runtime, "_redraw_host_ui"):
result = runtime.ensure_host(timeout=0.1)
self.assertIs(result, connection)
self.assertTrue(runtime._host_auto_start)
self.assertEqual(runtime.host_status()["state"], "READY")
finally:
runtime._host_auto_start = previous_auto_start
runtime._host_connection = previous_connection
runtime._host_snapshot = previous_snapshot
def test_blender_startup_does_not_start_ai_host(self):
runtime = self.addon.runtime
previous_auto_start = runtime._host_auto_start
previous_snapshot = runtime._host_snapshot
runtime._host_auto_start = False
runtime._host_snapshot = {"state": "STOPPED", "message": "Stopped"}
try:
with patch.object(
runtime,
"runtime_ready",
return_value=True,
), patch.object(
runtime,
"_discover_live_host",
return_value=(None, None),
), patch.object(
runtime,
"_close_finished_launched_host",
), patch.object(runtime, "start_host") as start_host:
interval = runtime._service_host_timer()
start_host.assert_not_called()
self.assertEqual(interval, 2.0)
self.assertEqual(runtime.host_status()["state"], "STOPPED")
finally:
runtime._host_auto_start = previous_auto_start
runtime._host_snapshot = previous_snapshot
def test_ben2_debug_job_uses_its_image_and_model(self):
settings = SimpleNamespace(
background_input_path="input.png",
device="CUDA",
)
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.BACKGROUND_REMOVAL_JOB
with TemporaryDirectory() as directory:
root = Path(directory)
input_path = root / "input.png"
input_path.touch()
settings.background_input_path = str(input_path)
paths = {
"log": root / "job.log",
"status": root / "job.json",
"result": root / "result",
}
with patch.object(
self.addon.runtime,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.runtime,
"runtime_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
) as model_ready, patch.object(
self.addon.runtime,
"remove_background_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as remove_background:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.runtime.HostJobRequest)
self.assertEqual(prepared_paths, paths)
model_ready.assert_called_once_with("BEN2_BASE")
self.assertEqual(remove_background.call_args.args[1], input_path)
self.assertEqual(remove_background.call_args.args[4], "BEN2_BASE")
def test_image_background_job_uses_operator_input(self):
settings = SimpleNamespace(device="AUTO")
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.IMAGE_BACKGROUND_JOB
with TemporaryDirectory() as directory:
root = Path(directory)
input_path = root / "input.png"
input_path.touch()
operator.snapform_input_path = str(input_path)
paths = {
"log": root / "job.log",
"status": root / "job.json",
"result": root / "result",
}
with patch.object(
self.addon.runtime,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.runtime,
"runtime_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"remove_background_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as remove_background:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.runtime.HostJobRequest)
self.assertEqual(prepared_paths, paths)
self.assertEqual(remove_background.call_args.args[1], input_path)
def test_image_background_job_accepts_video_input(self):
settings = SimpleNamespace(device="AUTO", video_frames=16)
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.IMAGE_BACKGROUND_JOB
with TemporaryDirectory() as directory:
root = Path(directory)
input_path = root / "clip.mp4"
input_path.touch()
operator.snapform_input_path = str(input_path)
paths = {
"log": root / "job.log",
"status": root / "job.json",
"result": root / "result",
}
with patch.object(
self.addon.runtime,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.runtime,
"runtime_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"remove_background_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as remove_background:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.runtime.HostJobRequest)
self.assertEqual(remove_background.call_args.args[1], input_path)
def test_original_frames_job_uses_frame_extraction_request(self):
settings = SimpleNamespace(device="AUTO", video_frames=16)
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.ORIGINAL_FRAMES_JOB
with TemporaryDirectory() as directory:
root = Path(directory)
input_path = root / "clip.mp4"
input_path.touch()
operator.snapform_input_path = str(input_path)
paths = {
"log": root / "job.log",
"status": root / "job.json",
"result": root / "result",
}
with patch.object(
self.addon.runtime,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.runtime,
"runtime_ready",
return_value=True,
), patch.object(
self.addon.runtime,
"extract_frames_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as extract_frames:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.runtime.HostJobRequest)
self.assertEqual(prepared_paths, paths)
self.assertEqual(extract_frames.call_args.args[1], input_path)
self.assertEqual(
extract_frames.call_args.kwargs["start_frame"],
1,
)
def test_completed_image_background_job_applies_result_with_undo(self):
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
apply_removed_background=lambda **options: calls.append(
("apply", options)
)
or {"FINISHED"},
),
ed=SimpleNamespace(
undo_push=lambda **options: calls.append(("undo", options))
or {"FINISHED"},
),
)
operator = self.addon.RunJob()
job = SimpleNamespace(
result_directory=Path("result"),
source_object_name="Reference",
)
operator._apply_removed_background(job)
self.assertEqual(
calls,
[
(
"apply",
{
"snapform_source_object_name": "Reference",
"snapform_output_path": str(
Path("result") / "foreground.png"
),
},
),
("undo", {"message": "Remove Background"}),
],
)
def test_completed_image_background_job_applies_foreground_sequence(self):
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
apply_removed_background=lambda **options: calls.append(
("apply", options)
)
or {"FINISHED"},
),
ed=SimpleNamespace(
undo_push=lambda **options: calls.append(("undo", options))
or {"FINISHED"},
),
)
operator = self.addon.RunJob()
with TemporaryDirectory() as directory:
result_directory = Path(directory)
(result_directory / "foreground.0000.png").touch()
(result_directory / "foreground.0001.png").touch()
job = SimpleNamespace(
result_directory=result_directory,
source_object_name="Reference",
)
operator._apply_removed_background(job)
self.assertEqual(
calls[0][1]["snapform_output_path"],
str(result_directory),
)
self.assertEqual(
calls[1],
("undo", {"message": "Remove Background"}),
)
def test_completed_original_background_job_builds_with_one_undo(self):
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
build_original_depth_plane=lambda **options: calls.append(
("build", options)
)
or {"FINISHED"},
),
ed=SimpleNamespace(
undo_push=lambda **options: calls.append(("undo", options))
or {"FINISHED"},
),
)
operator = self.addon.RunJob()
job = SimpleNamespace(
kind=self.addon.operators.ORIGINAL_BACKGROUND_JOB,
result_directory=Path("result"),
source_object_name="Reference",
subdivision_level=5,
delete_source=False,
displacement_via="MODIFIER",
unshaded=False,
)
operator._build_original_depth_plane(job)
self.assertEqual(calls[0][0], "build")
self.assertEqual(
calls[0][1]["snapform_background_path"],
str(Path("result") / "foreground.png"),
)
self.assertFalse(calls[0][1]["snapform_delete_source"])
self.assertEqual(
calls[1],
("undo", {"message": "Convert to Depth Plane"}),
)
def test_completed_original_frames_job_builds_with_color_frames(self):
calls = []
self.fake_bpy.ops = SimpleNamespace(
snapform=SimpleNamespace(
build_original_depth_plane=lambda **options: calls.append(
("build", options)
)
or {"FINISHED"},
),
ed=SimpleNamespace(
undo_push=lambda **options: calls.append(("undo", options))
or {"FINISHED"},
),
)
operator = self.addon.RunJob()
with TemporaryDirectory() as directory:
result_directory = Path(directory)
(result_directory / "frame.0001.jpg").touch()
(result_directory / "frame.0002.jpg").touch()
job = SimpleNamespace(
kind=self.addon.operators.ORIGINAL_FRAMES_JOB,
result_directory=result_directory,
source_object_name="Reference",
subdivision_level=5,
delete_source=False,
displacement_via="MODIFIER",
unshaded=False,
)
operator._build_original_depth_plane(job)
build_options = calls[0][1]
self.assertEqual(build_options["snapform_background_path"], "")
self.assertEqual(
build_options["snapform_color_frames_dir"],
str(result_directory),
)
self.assertEqual(
build_options["snapform_depth_frames_dir"],
str(result_directory),
)
self.assertFalse(build_options["snapform_delete_source"])
self.assertEqual(
calls[1],
("undo", {"message": "Convert to Depth Plane"}),
)
def test_ben2_download_command_filters_large_alternative_weights(self):
runtime = importlib.import_module("snapform.runtime")
with patch.object(
runtime,
"runtime_python",
return_value=Path("python"),
), patch.object(
runtime,
"model_directory",
return_value=Path("model"),
):
command = runtime.download_model_command(
"BEN2_BASE",
Path("status.json"),
)
patterns = [
command[index + 1]
for index, value in enumerate(command)
if value == "--allow-pattern"
]
self.assertEqual(patterns, ["config.json", "model.safetensors"])
label_index = command.index("--label")
self.assertEqual(command[label_index + 1], "BEN2 Base")
def test_install_runtime_command_has_no_model_label_reference(self):
runtime = importlib.import_module("snapform.runtime")
with patch.object(
runtime,
"uv_executable",
return_value="C:/uv/uv.exe",
), patch.object(
runtime,
"preferences",
return_value=SimpleNamespace(torch_backend="CU128"),
), patch.object(
runtime,
"runtime_directory",
return_value=Path("runtime"),
), patch.object(
runtime,
"models_directory",
return_value=Path("models"),
):
command = runtime.install_runtime_command(Path("status.json"))
self.assertNotIn("--label", command)
self.assertEqual(command[0], sys.executable)
self.assertIn("--uv", command)
self.assertIn("C:/uv/uv.exe", command)
self.assertTrue(
any("bootstrap_runtime.py" in str(value) for value in command)
)
def test_install_runtime_command_passes_configured_pypi_index(self):
runtime = importlib.import_module("snapform.runtime")
with patch.object(
runtime,
"uv_executable",
return_value="C:/uv/uv.exe",
), patch.object(
runtime,
"preferences",
return_value=SimpleNamespace(
torch_backend="CU128",
pypi_index_url="https://pypi.tuna.tsinghua.edu.cn/simple",
),
), patch.object(
runtime,
"runtime_directory",
return_value=Path("runtime"),
), patch.object(
runtime,
"models_directory",
return_value=Path("models"),
):
command = runtime.install_runtime_command(Path("status.json"))
index_position = command.index("--pypi-index")
self.assertEqual(
command[index_position + 1],
"https://pypi.tuna.tsinghua.edu.cn/simple",
)
def test_completed_export_opens_job_directory(self):
calls = []
self.fake_bpy.ops = SimpleNamespace(
wm=SimpleNamespace(
path_open=lambda **options: calls.append(options) or {"FINISHED"},
),
)
with TemporaryDirectory() as directory:
self.addon.operators.open_job_directory(directory)
self.assertEqual(len(calls), 1)
self.assertIn("filepath", calls[0])
def test_completed_job_removes_status_file(self):
operator = self.addon.RunJob()
with TemporaryDirectory() as directory:
status_path = Path(directory) / "status.json"
status_path.write_text(
'{"state": "succeeded", "progress": 1.0, "message": "done"}',
encoding="utf-8",
)
job = SimpleNamespace(
kind="INSTALL_RUNTIME",
cancelled=False,
log_file=None,
status_path=status_path,
)
context = SimpleNamespace(
scene=SimpleNamespace(
snapform_settings=SimpleNamespace(
status="",
progress=0.0,
),
),
)
with patch.object(operator, "_redraw_ui"):
result = operator._finish(context, job, 0)
self.assertEqual(result, {"FINISHED"})
self.assertFalse(status_path.exists())
def test_status_bar_draws_progress_and_cancel_button(self):
progress_calls = []
operator_calls = []
class Layout:
ui_units_x = 0.0
def row(self, align=False):
return self
def label(self, **_options):
return None
def progress(self, **options):
progress_calls.append(options)
def operator(self, operator, **options):
operator_calls.append((operator, options))
self.addon.operators._active_job = SimpleNamespace()
context = SimpleNamespace(
scene=SimpleNamespace(
snapform_settings=SimpleNamespace(
status="Processing",
progress=0.25,
),
),
)
self.addon.draw_status_bar(
SimpleNamespace(layout=Layout()),
context,
)
self.assertEqual(
progress_calls,
[{"factor": 0.25, "type": "BAR", "text": "Processing"}],
)
self.assertEqual(
operator_calls,
[("snapform.cancel_job", {"text": "", "icon": "X"})],
)
def test_ui_redraw_targets_status_bar_and_view_3d_areas(self):
redrawn = []
status_regions = (
SimpleNamespace(
tag_redraw=lambda: redrawn.append("status-region"),
),
)
areas = (
SimpleNamespace(
type="STATUSBAR",
tag_redraw=lambda: redrawn.append("status"),
regions=status_regions,
),
SimpleNamespace(
type="VIEW_3D",
tag_redraw=lambda: redrawn.append("view"),
regions=(),
),
)
context = SimpleNamespace(
window_manager=SimpleNamespace(
windows=(
SimpleNamespace(screen=SimpleNamespace(areas=areas)),
),
),
)
self.addon.status_bar.redraw_ui(context)
self.addon.status_bar.redraw_ui(context)
self.assertEqual(
redrawn,
[
"status",
"status-region",
"view",
"status",
"status-region",
"view",
],
)
def test_force_flag_refreshes_status_text(self):
redrawn = []
context = SimpleNamespace(
window_manager=SimpleNamespace(
windows=(
SimpleNamespace(
screen=SimpleNamespace(
areas=(
SimpleNamespace(
type="STATUSBAR",
tag_redraw=lambda: redrawn.append(
"status"
),
regions=(),
),
),
),
),
),
),
workspace=SimpleNamespace(
status_text_set_internal=lambda _value: redrawn.append(
"status-text"
),
),
)
self.addon.status_bar.redraw_ui(context, force=True)
self.assertEqual(
redrawn,
[
"status",
"status-text",
],
)
def test_force_flag_ignores_missing_workspace(self):
redrawn = []
context = SimpleNamespace(
window_manager=SimpleNamespace(
windows=(
SimpleNamespace(
screen=SimpleNamespace(
areas=(
SimpleNamespace(
type="STATUSBAR",
tag_redraw=lambda: redrawn.append(
"status"
),
regions=(),
),
),
),
),
),
),
)
self.addon.status_bar.redraw_ui(context, force=True)
self.assertEqual(redrawn, ["status"])