Files
AnyImage/tests/test_addon.py
nan 8aac4822c6 feat: Implement background depth distance feature for depth processing
- Added `background_depth_distance` property to `ActiveJob` and `RunJob` classes to manage background depth settings.
- Updated job execution to handle background depth distance, allowing for fixed negative displacement in depth calculations.
- Introduced new functions in the worker module to apply background depth distance to depth frames and manage original background depth.
- Enhanced tests to cover new functionality, ensuring correct behavior of background depth processing and reference depth extraction.
- Refactored job submission to run in a separate thread, improving responsiveness during execution.
2026-08-09 16:34:41 +08:00

2366 lines
84 KiB
Python

import importlib
import sys
import threading
import time
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_background_depth_distance = 2.0
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.assertEqual(calls[0]["snapform_background_depth_distance"], 2.0)
def test_original_depth_animation_extracts_frames_without_background(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_depth_source = "ORIGINAL"
operator.snapform_background_depth_distance = 0.0
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"]
)
background_offset = operator.__annotations__[
"snapform_background_depth_distance"
]
self.assertEqual(background_offset["default"], 0.0)
self.assertEqual(background_offset["min"], 0.0)
self.assertEqual(
operator.__annotations__["snapform_subdivision_level"]["default"],
5,
)
self.assertEqual(
set(operator.__annotations__),
{
"snapform_depth_source",
"snapform_displacement_via",
"snapform_unshaded",
"snapform_background_depth_distance",
"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_background_offset_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_background_depth_distance = 1.0
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")
self.assertEqual(calls[0]["snapform_background_depth_distance"], 1.0)
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",
)
self.assertEqual(
operator._format_status(
{
"progress": 0.25,
"message": "Downloading packages",
"stage": 2,
"stages": 3,
"stage_label": "PyTorch",
"eta_seconds": 125,
},
"fallback",
),
"2/3 PyTorch | Downloading packages | About 3 min left",
)
self.assertEqual(
operator._format_status(
{
"progress": 0.5,
"message": "",
"stage": 2,
"stages": 3,
"stage_label": "PyTorch",
},
"2/3 PyTorch | Downloading packages",
),
"2/3 PyTorch",
)
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_background_depth_distance = 2.5
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.assertEqual(calls[0]["snapform_background_depth_distance"], 2.5)
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_background_depth_distance = 1.0
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,
background_depth_distance=2.5,
)
self.assertEqual(
request.payload["parameters"]["remove_background_model_dir"],
str(Path("ben2_base")),
)
self.assertEqual(
request.payload["parameters"]["background_depth_distance"],
2.5,
)
def test_host_busy_snapshot_is_released_for_completed_local_job(self):
runtime = self.addon.runtime
previous_snapshot = runtime._host_snapshot
runtime._host_snapshot = {
"state": "BUSY",
"busy": True,
"active_job": {"job_id": "depth-job"},
}
try:
runtime.mark_host_job_complete("depth-job")
self.assertEqual(runtime.host_status()["state"], "READY")
self.assertFalse(runtime.host_busy())
finally:
runtime._host_snapshot = previous_snapshot
def test_job_submission_starts_without_blocking_execute(self):
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.IMAGE_DEPTH_JOB
operator.snapform_source_object_name = "Reference"
operator.snapform_depth_data_name = "img.depth"
operator.snapform_process_res = 1024
operator.snapform_subdivision_level = 5
operator.snapform_delete_source = False
operator.snapform_delete_input = False
operator.snapform_displacement_via = "MODIFIER"
operator.snapform_unshaded = False
operator.snapform_background_depth_distance = 0.0
started = threading.Event()
release = threading.Event()
def submit(_request):
started.set()
release.wait(timeout=2.0)
window_manager = SimpleNamespace(
event_timer_add=lambda *_args, **_kwargs: object(),
modal_handler_add=lambda *_args: None,
)
context = SimpleNamespace(
scene=SimpleNamespace(
snapform_settings=SimpleNamespace(status="", progress=0.0)
),
window_manager=window_manager,
window=object(),
)
paths = {
"log": Path("job.log"),
"status": Path("job.json"),
"result": Path("result"),
}
request = self.addon.runtime.HostJobRequest({"job_id": "job"})
try:
with patch.object(
operator,
"_prepare_command",
return_value=(request, paths),
), patch.object(
self.addon.runtime,
"submit_host_job",
side_effect=submit,
), patch.object(
self.addon.runtime,
"host_log_path",
return_value=Path("host.log"),
), patch.object(
self.addon.runtime,
"storage_root",
return_value=Path("."),
), patch.object(
self.addon.runtime,
"creation_flags",
return_value=0,
), patch.object(operator, "_redraw_ui"):
before = time.perf_counter()
result = operator.execute(context)
elapsed = time.perf_counter() - before
self.assertEqual(result, {"RUNNING_MODAL"})
self.assertLess(elapsed, 0.2)
self.assertTrue(started.wait(timeout=1.0))
finally:
release.set()
job = self.addon.operators.active_job()
if job is not None and job.start_thread is not None:
job.start_thread.join(timeout=2.0)
self.addon.operators.close_active_job()
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,
background_depth_distance=3.0,
)
parameters = request.payload["parameters"]
self.assertEqual(parameters["video_frames"], 24)
self.assertEqual(parameters["start_frame"], 70)
self.assertEqual(parameters["background_depth_distance"], 3.0)
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_device_enums_list_only_supported_devices(self):
runtime = importlib.import_module("snapform.runtime")
with patch.object(
runtime,
"supported_devices",
return_value=["cuda", "cpu"],
):
items = runtime.device_enum_items(None, None)
self.assertEqual([item[0] for item in items], ["CUDA", "CPU"])
self.assertEqual(items[0][2], "NVIDIA CUDA")
def test_device_enums_default_to_the_fastest_supported_device(self):
for class_type in (
self.addon.SnapFormSettings,
self.addon.SnapFormPreferences,
):
with self.subTest(class_type=class_type.__name__):
self.assertEqual(
class_type.__annotations__["device"]["default"],
0,
)
def test_preferences_expose_production_device(self):
preferences = self.addon.SnapFormPreferences
self.assertIn("device", preferences.__annotations__)
items_callback = preferences.__annotations__["device"]["items"]
with patch.object(
self.addon.runtime,
"supported_devices",
return_value=["cpu"],
):
items = items_callback(None, None)
self.assertEqual([item[0] for item in items], ["CPU"])
def test_remove_background_poll_requires_ben2_model(self):
context = SimpleNamespace(
object=SimpleNamespace(
type="EMPTY",
empty_display_type="IMAGE",
data=object(),
)
)
with patch.object(
self.addon.runtime,
"host_busy",
return_value=False,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=False,
):
self.assertFalse(self.addon.RemoveImageBackground.poll(context))
with patch.object(
self.addon.runtime,
"host_busy",
return_value=False,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=True,
):
self.assertTrue(self.addon.RemoveImageBackground.poll(context))
def test_convert_to_depth_plane_poll_requires_da3_model(self):
context = SimpleNamespace(
object=SimpleNamespace(
type="EMPTY",
empty_display_type="IMAGE",
data=object(),
)
)
with patch.object(
self.addon.runtime,
"host_busy",
return_value=False,
), patch.object(
self.addon.runtime,
"model_ready",
side_effect=lambda key: key == "BASE",
):
self.assertTrue(
self.addon.ConvertImageToDepthPlane.poll(context)
)
with patch.object(
self.addon.runtime,
"host_busy",
return_value=False,
), patch.object(
self.addon.runtime,
"model_ready",
return_value=False,
):
self.assertFalse(
self.addon.ConvertImageToDepthPlane.poll(context)
)
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_debug_jobs_use_the_scene_device(self):
settings = SimpleNamespace(
background_input_path="input.png",
device="CPU",
)
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,
), patch.object(
self.addon.runtime,
"remove_background_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as remove_background:
operator._prepare_command(None, settings)
self.assertEqual(
remove_background.call_args.kwargs["device"],
"CPU",
)
def test_production_jobs_use_the_preferences_device(self):
settings = SimpleNamespace(device="CPU", 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 / "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,
"production_device",
return_value="MPS",
), patch.object(
self.addon.runtime,
"remove_background_request",
return_value=self.addon.runtime.HostJobRequest(
{"job_id": "test"}
),
) as remove_background:
operator._prepare_command(None, settings)
self.assertEqual(
remove_background.call_args.kwargs["device"],
"MPS",
)
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[0][1]["snapform_depth_npz_path"],
str(Path("result") / "original-depth.npz"),
)
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,
"machine_devices",
return_value=["cuda", "cpu"],
), 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.assertEqual(
command[command.index("--backend") + 1],
"cu128",
)
self.assertTrue(
any("bootstrap_runtime.py" in str(value) for value in command)
)
def test_install_runtime_command_auto_selects_backend(self):
runtime = importlib.import_module("snapform.runtime")
with patch.object(
runtime,
"uv_executable",
return_value="C:/uv/uv.exe",
), patch.object(
runtime,
"machine_devices",
return_value=["cpu"],
), 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("--pypi-index", command)
self.assertEqual(
command[command.index("--backend") + 1],
"cpu",
)
self.assertIn("C:/uv/uv.exe", command)
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"])