Files
AnyImage/tests/test_addon.py
nan 2ad30bf4a3 feat: Implement ONNX model inference for BEN2, Depth Anything 3, and MoGe
- Added `ben2_onnx.py` for BEN2 ONNX inference with preprocessing.
- Introduced `da3_onnx.py` for Depth Anything 3 inference, including camera projection calculations.
- Created `moge_onnx.py` for MoGe-2 ONNX inference and post-processing.
- Implemented shared ONNX Runtime session helpers in `onnx_runtime.py`.
- Added model synchronization script `sync_models.py` for downloading and verifying ONNX models.
- Developed unit tests for BEN2, Depth Anything 3, and MoGe ONNX models to ensure functionality.
- Updated `vendor.lock.toml` to remove unused artifacts.
2026-08-14 10:27:09 +08:00

2739 lines
97 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
import numpy as np
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,
Menu=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),
),
OUTLINER_MT_object=SimpleNamespace(
append=lambda draw: cls.outliner_menu_draws.append(draw),
remove=lambda draw: cls.outliner_menu_draws.remove(draw),
),
)
cls.registered = []
cls.status_bar_draws = []
cls.context_menu_draws = []
cls.outliner_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.outliner_menu_draws.clear()
self.addon.operators._active_job = None
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.assertEqual(
self.outliner_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, [])
self.assertEqual(self.outliner_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.assertIn(
"snapform_color_frames_dir",
self.addon.BuildDepthPlane.__annotations__,
)
self.assertIn(
"snapform_depth_frames_dir",
self.addon.BuildDepthPlane.__annotations__,
)
self.assertIn("execute", self.addon.BuildDepthPlane.__dict__)
self.assertFalse(hasattr(self.addon, "ImportDepthPlane"))
def test_registers_perspective_mesh_operators(self):
self.assertIn(self.addon.ConvertImageToMesh, self.addon.CLASSES)
self.assertIn(self.addon.BuildImageMesh, self.addon.CLASSES)
self.assertEqual(self.addon.BuildImageMesh.bl_options, {"INTERNAL"})
self.assertIn(self.addon.SnapFormImageMenu, self.addon.CLASSES)
def test_image_context_menu_groups_actions_and_separates_background(self):
calls = []
class Layout:
operator_context = None
def operator(self, identifier, **_options):
calls.append(identifier)
def separator(self):
calls.append("separator")
menu = self.addon.SnapFormImageMenu()
menu.layout = Layout()
with patch.object(self.addon.client, "env_installed", return_value=True):
menu.draw(None)
self.assertEqual(
calls,
[
"snapform.convert_image_to_depth_plane",
"snapform.convert_image_to_mesh",
"separator",
"snapform.remove_image_background",
],
)
def test_image_context_menu_only_installs_env_when_missing(self):
calls = []
class Operator:
job_type = None
class Layout:
operator_context = None
def operator(self, identifier, **options):
operator = Operator()
calls.append((identifier, options, operator))
return operator
def separator(self):
calls.append("separator")
menu = self.addon.SnapFormImageMenu()
menu.layout = Layout()
with patch.object(self.addon.client, "env_installed", return_value=False):
menu.draw(None)
self.assertEqual(len(calls), 1)
identifier, options, operator = calls[0]
self.assertEqual(identifier, "snapform.run_job")
self.assertEqual(options["text"], "Install Environment")
self.assertEqual(operator.job_type, "INSTALL_ENV")
def test_image_mesh_reference_plane_matches_image_empty_bounds(self):
source_object = SimpleNamespace(
data=SimpleNamespace(size=(401, 201)),
empty_display_size=4.0,
empty_image_offset=(-0.25, -0.75),
)
intrinsics = np.asarray(
[[200.0, 0.0, 200.0], [0.0, 200.0, 100.0], [0.0, 0.0, 1.0]],
dtype=np.float32,
)
camera_vertices = np.asarray(
[
[-2.0, -1.0, 2.0],
[2.0, -1.0, 2.0],
[-2.0, 1.0, 2.0],
[2.0, 1.0, 2.0],
],
dtype=np.float32,
)
vertices, camera = self.addon.image_mesh.empty_anchored_vertices(
source_object,
camera_vertices,
intrinsics,
(401, 201),
2.0,
)
x_min, x_max, y_min, y_max = self.addon.image_depth.plane_bounds(
source_object
)
np.testing.assert_allclose(
vertices,
[
[x_min, y_max, 0.0],
[x_max, y_max, 0.0],
[x_min, y_min, 0.0],
[x_max, y_min, 0.0],
],
)
np.testing.assert_allclose(
camera["location"],
((x_min + x_max) * 0.5, (y_min + y_max) * 0.5, 2.0024922),
)
def test_mesh_conversion_dialog_only_keeps_model_and_camera_options(self):
annotations = self.addon.ConvertImageToMesh.__annotations__
for property_name in (
"snapform_confidence_percentile",
"snapform_edge_culling",
"snapform_face_reduction",
"snapform_depth_smoothing",
"snapform_postprocess_method",
):
with self.subTest(property_name=property_name):
self.assertNotIn(property_name, annotations)
self.assertFalse(annotations["snapform_create_camera"]["default"])
self.assertFalse(annotations["snapform_unshaded"]["default"])
self.assertEqual(annotations["snapform_unshaded"]["name"], "Unshaded")
self.assertFalse(annotations["snapform_remove_background"]["default"])
self.assertFalse(annotations["snapform_keep_original"]["default"])
self.assertEqual(
annotations["snapform_process_res_factor"]["default"],
100.0,
)
self.assertEqual(annotations["snapform_process_res_factor"]["min"], 1.0)
self.assertEqual(annotations["snapform_process_res_factor"]["max"], 100.0)
self.assertEqual(
annotations["snapform_process_res_factor"]["subtype"],
"PERCENTAGE",
)
self.assertEqual(
annotations["snapform_model"]["default"],
0,
)
def test_image_mesh_uses_o_prefixed_attributes_and_bundled_nodes(self):
image_mesh = self.addon.image_mesh
names = set(image_mesh.POINT_ATTRIBUTES) | set(
image_mesh.FACE_ATTRIBUTES
)
self.assertTrue(all(name.startswith("o_") for name in names))
self.assertTrue(image_mesh.node_asset_path().is_file())
self.assertEqual(image_mesh.NODE_GROUP_NAME, "O DA3 Surface")
self.assertEqual(image_mesh.NODE_ASSET_NAME, "O_SnapForm.blend")
def test_depth_plane_uses_bundled_nodes_and_material_attributes(self):
image_depth = self.addon.image_depth
self.assertTrue(image_depth.node_asset_path().is_file())
self.assertEqual(
image_depth.DEPTH_PLANE_NODE_GROUP_NAME,
"O DA3 Depth Plane",
)
self.assertEqual(
image_depth.MATERIAL_DISPLACEMENT_ATTRIBUTE,
"o_material_displacement",
)
self.assertEqual(
image_depth.BACKGROUND_DEPTH_ATTRIBUTE,
"o_background_depth",
)
def test_image_mesh_material_depth_scale_matches_fitted_geometry(self):
mesh_object = SimpleNamespace(scale=SimpleNamespace(z=-2.0))
self.assertEqual(
self.addon.image_mesh.material_depth_scale(
mesh_object,
{"scale_z": 0.25},
),
0.5,
)
def test_image_mesh_default_resolution_uses_double_reduction_estimate(self):
vertices = np.asarray(
((0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)),
dtype=np.float32,
)
faces = np.asarray(((0, 1, 2),), dtype=np.int32)
edge_lengths = np.asarray((1.0, 1.0, np.sqrt(2.0)))
self.assertAlmostEqual(
self.addon.image_mesh.default_remesh_resolution(vertices, faces),
float(np.median(edge_lengths) * np.sqrt(10.0) * 2.0),
)
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(result_image.alpha_mode, "CHANNEL_PACKED")
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(result_image.alpha_mode, "CHANNEL_PACKED")
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(result_image.alpha_mode, "CHANNEL_PACKED")
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_model = "ORIGINAL"
operator.snapform_unshaded = False
operator.snapform_remove_background = True
operator.snapform_keep_original = True
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.assertNotIn("snapform_background_depth_distance", calls[0])
def test_original_depth_animation_extracts_frames_and_background(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_model = "ORIGINAL"
operator.snapform_unshaded = False
operator.snapform_remove_background = True
operator.snapform_keep_original = True
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_BACKGROUND")
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_original_depth_without_background_uses_frame_extraction(self):
operator = self.addon.ConvertImageToDepthPlane()
operator.snapform_model = "ORIGINAL"
operator.snapform_unshaded = False
operator.snapform_remove_background = False
operator.snapform_keep_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_FRAMES")
self.assertTrue(calls[0]["snapform_delete_source"])
def test_conversion_dialog_exposes_requested_settings(self):
operator = self.addon.ConvertImageToDepthPlane
self.assertEqual(
operator.__annotations__["snapform_model"]["default"],
0,
)
self.assertFalse(operator.__annotations__["snapform_use_ray_pose"]["default"])
self.assertEqual(
operator.__annotations__["snapform_process_res_factor"]["default"],
100.0,
)
self.assertEqual(
operator.__annotations__["snapform_process_res_factor"]["min"],
1.0,
)
self.assertEqual(
operator.__annotations__["snapform_process_res_factor"]["max"],
100.0,
)
self.assertFalse(
operator.__annotations__["snapform_remove_background"]["default"]
)
self.assertFalse(
operator.__annotations__["snapform_keep_original"]["default"]
)
self.assertFalse(
operator.__annotations__["snapform_unshaded"]["default"]
)
self.assertEqual(
set(operator.__annotations__),
{
"snapform_unshaded",
"snapform_model",
"snapform_use_ray_pose",
"snapform_process_res_factor",
"snapform_moge_resolution_level",
"snapform_remove_background",
"snapform_keep_original",
},
)
self.assertEqual(
operator.__annotations__["snapform_model"]["name"],
"Depth From",
)
self.assertEqual(
operator.__annotations__["snapform_unshaded"]["name"],
"Unshaded",
)
model_items_callback = operator.__annotations__["snapform_model"]["items"]
with patch.object(
self.addon.client,
"model_ready",
return_value=True,
):
model_items = model_items_callback(None, None)
self.assertEqual(
[item[0] for item in model_items],
[
"MOGE2_VITS_NORMAL",
"MOGE2_VITB_NORMAL",
"MOGE2_VITL_NORMAL",
"SMALL",
"BASE",
"ORIGINAL",
],
)
def test_conversion_dialog_leaves_last_properties_to_blender(self):
operator = self.addon.ConvertImageToDepthPlane()
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,
),
)
result = operator.invoke(context, None)
self.assertIs(result, operator)
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_mask_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_model = "BASE"
operator.snapform_use_ray_pose = True
operator.snapform_process_res_factor = 50.0
operator.snapform_unshaded = False
operator.snapform_remove_background = True
operator.snapform_keep_original = True
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.assertTrue(calls[0]["snapform_remove_background"])
self.assertNotIn("snapform_background_depth_distance", calls[0])
def test_conversion_dialog_marks_missing_models(self):
items_callback = self.addon.ConvertImageToDepthPlane.__annotations__[
"snapform_model"
]["items"]
with patch.object(
self.addon.client,
"model_ready",
side_effect=lambda key: key in {"SMALL", "BASE"},
):
items = items_callback(None, None)
self.assertEqual(
[item[0] for item in items],
[
"MOGE2_VITS_NORMAL",
"MOGE2_VITB_NORMAL",
"MOGE2_VITL_NORMAL",
"SMALL",
"BASE",
"ORIGINAL",
],
)
labels = {item[0]: item[1] for item in items}
self.assertEqual(labels["BASE"], "DA3 Base")
self.assertEqual(labels["MOGE2_VITB_NORMAL"], "MoGe-2 ViT-B Normal (Missing)")
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.client,
"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_model = "GIANT_1_1"
operator.snapform_use_ray_pose = True
operator.snapform_process_res_factor = 50.0
operator.snapform_unshaded = True
operator.snapform_remove_background = True
operator.snapform_keep_original = False
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.client,
"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"], 966)
self.assertEqual(calls[0]["snapform_model_key"], "GIANT_1_1")
self.assertTrue(calls[0]["snapform_remove_background"])
self.assertTrue(calls[0]["snapform_unshaded"])
self.assertEqual(calls[0]["snapform_depth_data_name"], "img.depth")
self.assertTrue(calls[0]["snapform_delete_input"])
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.client,
"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.client,
"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),
("GIANT_1_1", False),
("NESTED_GIANT_LARGE_1_1", True),
):
with self.subTest(model=snapform_model), patch.object(
self.addon.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
) as model_ready, patch.object(
self.addon.client,
"reconstruct_request",
return_value=self.addon.client.JobRequest(
{"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.client.JobRequest,
)
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_mask_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.client,
"new_job_paths",
return_value={
"log": Path(directory) / "job.log",
"status": Path(directory) / "job.json",
"result": Path(directory) / "result",
},
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"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_service_request(self):
client = importlib.import_module("snapform.client")
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(
client,
"venv_python",
return_value=Path("python"),
), patch.object(
client,
"model_directory",
side_effect=lambda key: Path(key.lower()),
):
request = client.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")),
)
self.assertNotIn(
"background_depth_distance",
request.payload["parameters"],
)
def test_service_busy_snapshot_is_released_for_completed_local_job(self):
client = self.addon.client
previous_snapshot = client._service_snapshot
client._service_snapshot = {
"state": "BUSY",
"busy": True,
"active_job": {"job_id": "depth-job"},
}
try:
client.mark_service_job_complete("depth-job")
self.assertEqual(client.service_status()["state"], "READY")
self.assertFalse(client.service_busy())
finally:
client._service_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_delete_source = False
operator.snapform_delete_input = False
operator.snapform_unshaded = False
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.client.JobRequest({"job_id": "job"})
try:
with patch.object(
operator,
"_prepare_command",
return_value=(request, paths),
), patch.object(
self.addon.client,
"submit_service_job",
side_effect=submit,
), patch.object(
self.addon.client,
"service_log_path",
return_value=Path("host.log"),
), patch.object(
self.addon.client,
"storage_root",
return_value=Path("."),
), patch.object(
self.addon.client,
"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):
client = importlib.import_module("snapform.client")
self.assertEqual(client.DEFAULT_STORAGE_ROOT.name, ".snapform")
self.assertEqual(
self.addon.SnapFormPreferences.__annotations__["storage_root"][
"default"
],
str(client.DEFAULT_STORAGE_ROOT),
)
def test_ai_service_uses_loopback_address(self):
client = importlib.import_module("snapform.client")
self.assertEqual(client.SERVICE_ADDRESS, "127.0.0.1")
def test_closing_blender_terminates_service_it_started(self):
client = importlib.import_module("snapform.client")
previous_connection = client._service_connection
previous_registered = client._registered_instance_id
client._service_connection = SimpleNamespace(port=12345)
client._registered_instance_id = "instance"
try:
with patch.object(
client,
"_service_http_request",
) as request, patch.object(
client,
"_close_launched_service",
) as close_service:
client.detach_service_client()
request.assert_called_once()
self.assertEqual(request.call_args.args[1], "DELETE")
close_service.assert_called_once_with(terminate=True)
finally:
client._service_connection = previous_connection
client._registered_instance_id = previous_registered
self.assertIsNone(client._service_connection)
self.assertIsNone(client._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):
client = importlib.import_module("snapform.client")
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(
client,
"venv_python",
return_value=Path("python"),
), patch.object(
client,
"model_directory",
return_value=Path("model"),
):
request = client.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):
client = importlib.import_module("snapform.client")
settings = SimpleNamespace(device="AUTO", video_frames=24)
with patch.object(
client,
"venv_python",
return_value=Path("python"),
), patch.object(
client,
"model_directory",
return_value=Path("ben2_base"),
):
request = client.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)
self.assertNotIn("background_depth_distance", parameters)
def test_extract_frames_request_passes_frame_range(self):
client = importlib.import_module("snapform.client")
settings = SimpleNamespace(device="AUTO", video_frames=24)
request = client.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):
client = importlib.import_module("snapform.client")
with TemporaryDirectory() as directory:
jobs_root = Path(directory)
with patch.object(
client,
"jobs_directory",
return_value=jobs_root,
):
paths = client.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["log"].name, "job.log")
self.assertEqual(paths["status"].name, "status.json")
self.assertEqual(
client.job_id_from_status(paths["status"]),
paths["result"].name,
)
def test_new_job_paths_uses_unique_action_object_names(self):
client = importlib.import_module("snapform.client")
with TemporaryDirectory() as directory:
jobs_root = Path(directory)
with patch.object(
client,
"jobs_directory",
return_value=jobs_root,
):
depth_paths = client.new_job_paths(
self.addon.operators.IMAGE_DEPTH_JOB
)
background_paths = client.new_job_paths(
self.addon.operators.IMAGE_BACKGROUND_JOB
)
frames_paths = client.new_job_paths(
self.addon.operators.ORIGINAL_FRAMES_JOB
)
self.assertRegex(
depth_paths["result"].name,
r"^generate-depth-plane_\d{8}-\d{6}-\d{6}$",
)
self.assertTrue(
background_paths["result"].name.startswith(
"remove-image-background_"
)
)
self.assertTrue(
frames_paths["result"].name.startswith(
"extract-original-frames_"
)
)
def test_generated_inputs_are_moved_into_job_root(self):
client = importlib.import_module("snapform.client")
with TemporaryDirectory() as directory:
root = Path(directory)
staging = root / "staging"
staging.mkdir()
(staging / "frame.0001.jpg").write_bytes(b"first")
(staging / "frame.0002.jpg").write_bytes(b"second")
job_directory = root / "job"
job_directory.mkdir()
result = client.move_input_into_job(staging, job_directory)
self.assertEqual(result, job_directory)
self.assertFalse(staging.exists())
self.assertEqual(
(job_directory / "frame.0001.jpg").read_bytes(),
b"first",
)
self.assertEqual(
(job_directory / "frame.0002.jpg").read_bytes(),
b"second",
)
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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
), patch.object(
self.addon.client,
"reconstruct_request",
return_value=self.addon.client.JobRequest(
{"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):
client = importlib.import_module("snapform.client")
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(
client,
"venv_python",
return_value=Path("python"),
), patch.object(
client,
"model_directory",
return_value=Path("large-model"),
) as snapform_model_directory:
request = client.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_reconstruct_request_enables_ben2_for_image_mesh(self):
client = importlib.import_module("snapform.client")
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(
client,
"venv_python",
return_value=Path("python"),
), patch.object(
client,
"model_directory",
side_effect=lambda key: Path(key.lower()),
):
request = client.reconstruct_request(
settings,
"mesh_data",
Path("status.json"),
Path("result"),
remove_background=True,
)
parameters = request.payload["parameters"]
self.assertNotIn("mesh_confidence_percentile", parameters)
self.assertNotIn("mesh_edge_culling", parameters)
self.assertNotIn("mesh_face_reduction", parameters)
self.assertNotIn("mesh_depth_smoothing", parameters)
self.assertNotIn("mesh_postprocess_method", parameters)
self.assertEqual(
parameters["remove_background_model_dir"],
str(Path("ben2_base")),
)
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", "mesh"],
)
self.assertEqual(export_format["default"], "glb")
self.assertEqual(export_format["name"], "Output Type")
self.assertEqual(
[item[1] for item in export_format["items"]],
[
"Point Cloud (GLB)",
"Gaussian Splats (PLY)",
"Depth Preview (Images)",
"Depth Data (NPZ)",
"Textured Surface (GLB)",
],
)
def test_mesh_is_a_da3_output_type(self):
settings = SimpleNamespace(
input_path="",
export_format="mesh",
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="CPU",
use_ray_pose=False,
ref_view_strategy="saddle_balanced",
)
operator = self.addon.RunJob()
operator.job_type = self.addon.operators.RECONSTRUCTION_JOB
with TemporaryDirectory() as directory:
input_path = Path(directory) / "input.png"
input_path.touch()
settings.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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
), patch.object(
self.addon.client,
"reconstruct_request",
return_value=self.addon.client.JobRequest(
{"job_id": "test"}
),
) as reconstruct:
operator._prepare_command(None, settings)
self.assertEqual(reconstruct.call_args.args[1], "mesh")
self.assertEqual(reconstruct.call_args.kwargs["model_key"], "BASE")
def test_confidence_threshold_is_a_bounded_percentage(self):
confidence = self.addon.SnapFormSettings.__annotations__[
"conf_thresh_percentile"
]
self.assertEqual(confidence["name"], "Confidence Threshold")
self.assertEqual(confidence["min"], 0.0)
self.assertEqual(confidence["max"], 100.0)
self.assertEqual(confidence["subtype"], "PERCENTAGE")
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,
[],
)
def test_device_enums_list_only_supported_devices(self):
client = importlib.import_module("snapform.client")
with patch.object(
client,
"supported_devices",
return_value=["cuda", "cpu"],
):
items = client.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.client,
"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.client,
"service_busy",
return_value=False,
), patch.object(
self.addon.client,
"model_ready",
return_value=False,
):
self.assertFalse(self.addon.RemoveImageBackground.poll(context))
with patch.object(
self.addon.client,
"service_busy",
return_value=False,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
):
self.assertTrue(self.addon.RemoveImageBackground.poll(context))
def test_convert_to_depth_plane_poll_requires_da3_and_ben2_models(self):
context = SimpleNamespace(
object=SimpleNamespace(
type="EMPTY",
empty_display_type="IMAGE",
data=object(),
)
)
with patch.object(
self.addon.client,
"service_busy",
return_value=False,
), patch.object(
self.addon.client,
"model_ready",
side_effect=lambda key: key in {"BASE", "BEN2_BASE"},
):
self.assertTrue(
self.addon.ConvertImageToDepthPlane.poll(context)
)
with patch.object(
self.addon.client,
"service_busy",
return_value=False,
), patch.object(
self.addon.client,
"model_ready",
return_value=False,
):
self.assertTrue(
self.addon.ConvertImageToDepthPlane.poll(context)
)
def test_debug_panel_is_closed_by_default(self):
self.assertIn(
"DEFAULT_CLOSED",
self.addon.DebugPanel.bl_options,
)
panels = (
self.addon.Moge2ApiDebugPanel,
self.addon.Da3OutputDebugPanel,
self.addon.Ben2RemovalDebugPanel,
)
for panel in panels:
self.assertEqual(panel.bl_parent_id, self.addon.DebugPanel.bl_idname)
self.assertIn("DEFAULT_CLOSED", panel.bl_options)
self.assertEqual(
[panel.bl_label for panel in panels],
["MoGe-2", "DA3", "BEN2"],
)
def test_service_panel_exposes_shared_service_state_and_controls(self):
self.assertIn(self.addon.AiServicePanel, self.addon.CLASSES)
self.assertIn(self.addon.ControlService, self.addon.CLASSES)
self.assertEqual(self.addon.AiServicePanel.bl_label, "AI Service")
def test_models_is_an_independent_closed_panel(self):
self.assertIn(self.addon.ModelsPanel, self.addon.CLASSES)
self.assertNotIn("bl_parent_id", self.addon.ModelsPanel.__dict__)
self.assertIn("DEFAULT_CLOSED", self.addon.ModelsPanel.bl_options)
def test_ai_operation_restarts_a_manually_stopped_service(self):
client = self.addon.client
connection = client.Connection(51234, "token", 42, "host")
health = {
"instance_id": "host",
"busy": False,
"clients": 1,
"cached_models": {},
}
previous_auto_start = client._service_auto_start
previous_connection = client._service_connection
previous_snapshot = client._service_snapshot
client._service_auto_start = False
client._service_connection = None
client._service_snapshot = {"state": "STOPPED", "message": "Stopped"}
try:
with patch.object(
client,
"_discover_live_service",
return_value=(connection, health),
), patch.object(
client,
"_register_service_client",
), patch.object(
client,
"_check_service",
return_value=health,
), patch.object(client, "_redraw_service_ui"):
result = client.ensure_service(timeout=0.1)
self.assertIs(result, connection)
self.assertTrue(client._service_auto_start)
self.assertEqual(client.service_status()["state"], "READY")
finally:
client._service_auto_start = previous_auto_start
client._service_connection = previous_connection
client._service_snapshot = previous_snapshot
def test_blender_startup_does_not_start_ai_service(self):
client = self.addon.client
previous_auto_start = client._service_auto_start
previous_snapshot = client._service_snapshot
client._service_auto_start = False
client._service_snapshot = {"state": "STOPPED", "message": "Stopped"}
try:
with patch.object(
client,
"env_installed",
return_value=True,
), patch.object(
client,
"_discover_live_service",
return_value=(None, None),
), patch.object(
client,
"_close_finished_launched_service",
), patch.object(client, "start_service") as start_service:
interval = client._service_timer()
start_service.assert_not_called()
self.assertEqual(interval, 2.0)
self.assertEqual(client.service_status()["state"], "STOPPED")
finally:
client._service_auto_start = previous_auto_start
client._service_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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
) as model_ready, patch.object(
self.addon.client,
"remove_background_request",
return_value=self.addon.client.JobRequest(
{"job_id": "test"}
),
) as remove_background:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.client.JobRequest)
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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
), patch.object(
self.addon.client,
"remove_background_request",
return_value=self.addon.client.JobRequest(
{"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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
), patch.object(
self.addon.client,
"production_device",
return_value="MPS",
), patch.object(
self.addon.client,
"remove_background_request",
return_value=self.addon.client.JobRequest(
{"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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
), patch.object(
self.addon.client,
"remove_background_request",
return_value=self.addon.client.JobRequest(
{"job_id": "test"}
),
) as remove_background:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.client.JobRequest)
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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"model_ready",
return_value=True,
), patch.object(
self.addon.client,
"remove_background_request",
return_value=self.addon.client.JobRequest(
{"job_id": "test"}
),
) as remove_background:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.client.JobRequest)
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.client,
"new_job_paths",
return_value=paths,
), patch.object(
self.addon.client,
"env_installed",
return_value=True,
), patch.object(
self.addon.client,
"extract_frames_request",
return_value=self.addon.client.JobRequest(
{"job_id": "test"}
),
) as extract_frames:
command, prepared_paths = operator._prepare_command(
None,
settings,
)
self.assertIsInstance(command, self.addon.client.JobRequest)
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"],
"",
)
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):
client = importlib.import_module("snapform.client")
with patch.object(
client,
"venv_python",
return_value=Path("python"),
), patch.object(
client,
"model_directory",
return_value=Path("model"),
):
command = client.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, ["onnx/model_fp16.onnx"])
label_index = command.index("--label")
self.assertEqual(command[label_index + 1], "BEN2 Base")
def test_install_env_command_has_no_model_label_reference(self):
client = importlib.import_module("snapform.client")
with patch.object(
client,
"uv_executable",
return_value="C:/uv/uv.exe",
), patch.object(
client,
"machine_devices",
return_value=["cuda", "cpu"],
), patch.object(
client,
"venv_directory",
return_value=Path(".venv"),
), patch.object(
client,
"models_directory",
return_value=Path("models"),
):
command = client.install_env_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.assertNotIn("--torch-variant", command)
self.assertTrue(
any("install_env.py" in str(value) for value in command)
)
def test_install_env_command_does_not_select_legacy_runtime(self):
client = importlib.import_module("snapform.client")
with patch.object(
client,
"uv_executable",
return_value="C:/uv/uv.exe",
), patch.object(
client,
"machine_devices",
return_value=["cpu"],
), patch.object(
client,
"venv_directory",
return_value=Path(".venv"),
), patch.object(
client,
"models_directory",
return_value=Path("models"),
):
command = client.install_env_command(Path("status.json"))
self.assertNotIn("--pypi-index", command)
self.assertNotIn("--torch-variant", command)
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_ENV",
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"])