import importlib import inspect 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) 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" 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_completed_depth_job_uses_undoable_build_operator(self): source = inspect.getsource( self.addon.RunJob._build_depth_plane ) self.assertIn("bpy.ops.snapform.build_depth_plane(", source) self.assertIn("snapform_unshaded=job.unshaded", source) self.assertIn( "snapform_depth_data_name=job.depth_data_name", source, ) self.assertIn('if "FINISHED" not in result:', source) self.assertIn( 'bpy.ops.ed.undo_push(message="Convert to Depth Plane")', source, ) self.assertIn('if "FINISHED" not in undo_result:', source) def test_image_context_menu_exposes_depth_and_background_actions(self): draw_source = inspect.getsource( self.addon.draw_image_context_menu ) self.assertIn( '"snapform.convert_image_to_depth_plane"', draw_source, ) self.assertIn( '"snapform.remove_image_background"', draw_source, ) self.assertIn('text="Remove Background"', draw_source) self.assertEqual(draw_source.count("row = self.layout.row()"), 2) self.assertIn('operator_context = "INVOKE_DEFAULT"', draw_source) self.assertNotIn("import_depth_plane", draw_source) self.assertNotIn("Rebuild", draw_source) 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.rb") 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.rb", ) 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.rb") self.assertEqual(pack_calls, [True]) 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=object()) 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_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", ) draw_source = inspect.getsource(operator.draw) self.assertIn( 'if self.snapform_depth_source == "ESTIMATE":', draw_source, ) self.assertLess( draw_source.index( '"snapform_process_res_factor",' ), draw_source.index( 'settings_body.prop(self, "snapform_subdivision_level")' ), ) self.assertIn( 'text=f"Resolution Scale ({process_res} px)"', draw_source, ) self.assertIn("estimate_box = layout.box()", draw_source) self.assertIn( 'estimate_box.prop(self, "snapform_model")', draw_source, ) self.assertIn( 'estimate_box.prop(self, "snapform_use_ray_pose")', draw_source, ) self.assertIn( 'default_closed=True', draw_source, ) self.assertIn('settings_header.label(text="Options")', draw_source) self.assertIn('settings_header.label(text="Options")', draw_source) self.assertNotIn("layout.separator", draw_source) setting_fields = ( "snapform_displacement_via", "snapform_subdivision_level", "snapform_remove_background", "snapform_unshaded", ) field_positions = [ draw_source.index(f'settings_body.prop(self, "{field}")') for field in setting_fields ] self.assertEqual(field_positions, sorted(field_positions)) 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", ], ) self.assertIn("invoke_props_dialog", inspect.getsource(operator.invoke)) self.assertNotIn( '"DA3"', inspect.getsource(operator), ) def test_conversion_dialog_remembers_last_depth_model(self): operator = self.addon.ConvertImageToDepthPlane() operator.properties = SimpleNamespace( is_property_set=lambda _name: False, ) context = SimpleNamespace( 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_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"]) draw_source = inspect.getsource(self.addon.ConvertImageToDepthPlane.draw) self.assertIn('text="No DA3 models installed"', draw_source) self.assertIn('text="Download one in Host > Models"', draw_source) self.assertNotIn("_dialog_requirements_ready", inspect.getsource(self.addon.ConvertImageToDepthPlane)) def test_depth_conversion_progress_is_provider_neutral(self): starting_source = inspect.getsource( self.addon.RunJob._starting_message ) finish_source = inspect.getsource(self.addon.RunJob._finish) self.assertIn('return "Starting depth estimation"', starting_source) self.assertIn('"Depth plane is ready"', finish_source) self.assertNotIn("DA3 image depth", starting_source) self.assertNotIn("DA3 depth plane", finish_source) def test_depth_background_is_loaded_without_changing_source_empty(self): source = inspect.getsource(self.addon.RunJob._build_depth_plane) self.assertIn("snapform_background_path=", source) self.assertNotIn("_apply_removed_background", source) 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_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_authenticated_loopback_http(self): runtime = importlib.import_module("snapform.runtime") request_source = inspect.getsource(runtime._host_http_request) start_source = inspect.getsource(runtime.start_host) self.assertEqual(runtime.HOST_ADDRESS, "127.0.0.1") self.assertIn('"X-SnapForm-Token"', request_source) self.assertIn('"/health"', inspect.getsource(runtime._check_host)) self.assertIn('with_name("ai_host.py")', start_source) self.assertIn("ProxyHandler({})", inspect.getsource(runtime)) self.assertIn('return storage_root() / "host.json"', inspect.getsource(runtime.host_discovery_path)) def test_host_jobs_complete_from_status_state(self): source = inspect.getsource(self.addon.RunJob.modal) self.assertIn("if job.uses_host:", source) self.assertIn('state == "succeeded"', source) self.assertIn('{"failed", "cancelled"}', source) 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_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_debug_panel_uses_one_export_button_and_dynamic_settings(self): debug_source = inspect.getsource(self.addon.DebugPanel.draw) da3_source = inspect.getsource(self.addon.Da3DebugPanel.draw) ben2_source = inspect.getsource(self.addon.Ben2DebugPanel.draw) self.assertIn('layout.prop(settings, "device")', debug_source) self.assertEqual(da3_source.count("export_row.operator("), 1) self.assertIn('settings.export_format == "glb"', da3_source) self.assertIn('settings.export_format == "gs_ply"', da3_source) self.assertIn('"gs_model" if settings.export_format', da3_source) self.assertIn("layout.prop(settings, model_property)", da3_source) self.assertNotIn("model_row", da3_source) self.assertIn("and model_ready(selected_model_key(settings))", da3_source) self.assertNotIn('text="Ready"', da3_source) self.assertNotIn('text="Missing"', da3_source) self.assertNotIn("License:", da3_source) self.assertIn('operator.job_type = "RECONSTRUCT"', da3_source) self.assertIn( 'layout.prop(settings, "background_input_path")', ben2_source, ) self.assertIn('text="Remove Background"', ben2_source) self.assertIn("and model_ready(BEN2_MODEL.key)", ben2_source) self.assertIn('operator.job_type = "REMOVE_BACKGROUND"', ben2_source) 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) source = inspect.getsource(self.addon.HostPanel.draw) self.assertEqual(self.addon.HostPanel.bl_label, "Host") self.assertIn('text="Runtime is not installed"', source) self.assertIn('text="Install Runtime"', source) self.assertIn("runtime.host_status()", source) self.assertIn('f"Running (Clients:', source) self.assertIn('else "Running (Busy)"', source) self.assertIn('"READY": "RADIOBUT_ON"', source) self.assertIn('"BUSY": "TIME"', source) self.assertNotIn('"READY": "PLAY"', source) self.assertNotIn('text=f"Task:', source) self.assertIn('elif state == "STOPPED":', source) self.assertNotIn("Blender Clients:", source) self.assertNotIn("status['port']", source) self.assertIn('icon="TRASH"', source) self.assertIn('icon="FILE_REFRESH"', source) self.assertIn('icon="PAUSE"', source) self.assertNotIn('text="Open Log"', source) preferences_source = inspect.getsource(self.addon.SnapFormPreferences.draw) self.assertIn('text="Open Host Log"', preferences_source) def test_host_start_control_does_not_wait_for_model_runtime(self): source = inspect.getsource(self.addon.runtime.enable_host) self.assertIn("start_host()", source) self.assertNotIn("ensure_host()", source) 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_models_panel_is_collapsed_and_assigns_each_download_target(self): draw_source = inspect.getsource( self.addon.ModelsPanel.draw ) model_source = inspect.getsource( self.addon.ModelsPanel._draw_model ) self.assertIn("for model in MODELS.values()", draw_source) self.assertIn( "self._draw_model(layout, BEN2_MODEL, running)", draw_source, ) self.assertIn("operator.model_key = model.key", model_source) self.assertIn( "download_row.enabled = runtime_ready() and not running", model_source, ) self.assertNotIn('label(text="Not downloaded"', model_source) self.assertNotIn("Isolated Runtime ready", draw_source) self.assertNotIn("License:", model_source) self.assertEqual( self.addon.ModelsPanel.bl_label, "Models", ) self.assertEqual( self.addon.ModelsPanel.bl_parent_id, self.addon.HostPanel.bl_idname, ) self.assertEqual( self.addon.ModelsPanel.bl_options, {"DEFAULT_CLOSED"}, ) 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_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_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( 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_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"]) def test_preferences_install_button_is_disabled_while_busy(self): draw_source = inspect.getsource(self.addon.SnapFormPreferences.draw) self.assertIn( "install_row.enabled = active_job() is None", draw_source, ) 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_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_image_depth_status_uses_same_progress_layout(self): progress_calls = [] operator_calls = [] class Layout: 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( kind=self.addon.operators.IMAGE_DEPTH_JOB, ) context = SimpleNamespace( scene=SimpleNamespace( snapform_settings=SimpleNamespace( status="Estimating depth", progress=0.25, ), ), ) self.addon.draw_status_bar( SimpleNamespace(layout=Layout()), context, ) self.assertEqual( progress_calls, [ { "factor": 0.25, "type": "BAR", "text": "Estimating depth", } ], ) self.assertEqual( operator_calls, [("snapform.cancel_job", {"text": "", "icon": "X"})], ) def test_progress_is_appended_before_cancel_button(self): register_source = inspect.getsource(self.addon.register) draw_source = inspect.getsource(self.addon.draw_status_bar) self.assertIn("STATUSBAR_HT_header.append", register_source) self.assertNotIn("STATUSBAR_HT_header.prepend", register_source) self.assertLess( draw_source.index("progress_row.progress("), draw_source.index('"snapform.cancel_job"'), ) self.assertIn("progress_row.ui_units_x = 12.0", draw_source) self.assertNotIn('label(text="SnapForm"', draw_source) 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_does_not_trigger_window_draw_and_swap(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"]) source = inspect.getsource(self.addon.status_bar) self.assertNotIn("redraw_timer", source) self.assertNotIn("DRAW_WIN_SWAP", source)