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