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( prepend=lambda draw: cls.status_bar_draws.append(draw), remove=lambda draw: cls.status_bar_draws.remove(draw), ), ) cls.registered = [] cls.status_bar_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("omoo_da3") 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 == "omoo_da3" or module_name.startswith("omoo_da3."): sys.modules.pop(module_name) def tearDown(self): self.registered.clear() self.status_bar_draws.clear() self.addon.operators._active_job = None if hasattr(self.fake_bpy.types.Scene, "omoo_da3_settings"): del self.fake_bpy.types.Scene.omoo_da3_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, "omoo_da3_settings") ) self.assertEqual( self.status_bar_draws, [self.addon.draw_status_bar], ) self.addon.unregister() self.assertEqual(self.registered, []) self.assertFalse( hasattr(self.fake_bpy.types.Scene, "omoo_da3_settings") ) self.assertEqual(self.status_bar_draws, []) def test_preferences_use_package_identifier(self): self.assertEqual( self.addon.OMOO_DA3_preferences.bl_idname, "omoo_da3", ) def test_default_storage_is_shared_in_user_home(self): runtime = importlib.import_module("omoo_da3.runtime") self.assertEqual(runtime.DEFAULT_STORAGE_ROOT.name, ".omoo_da3") self.assertEqual( self.addon.OMOO_DA3_preferences.__annotations__["storage_root"][ "default" ], str(runtime.DEFAULT_STORAGE_ROOT), ) def test_reconstruct_command_preserves_da3_parameter_names(self): runtime = importlib.import_module("omoo_da3.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"), ): command = runtime.reconstruct_command( settings, "gs_ply", Path("status.json"), Path("result"), ) self.assertIn("--export-format", command) self.assertIn("gs_ply", command) 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, command) def test_export_format_property_includes_all_da3_formats(self): export_format = self.addon.OMOO_DA3_PG_settings.__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.OMOO_DA3_PG_settings.__annotations__[ "gs_model" ] identifiers = [item[0] for item in gs_model["items"]] 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.OMOO_DA3_PT_debug.bl_options, ) def test_debug_panel_uses_one_export_button_and_dynamic_settings(self): draw_source = inspect.getsource( self.addon.OMOO_DA3_PT_debug.draw ) self.assertEqual(draw_source.count("layout.operator("), 1) self.assertIn('settings.export_format == "glb"', draw_source) self.assertIn('settings.export_format == "gs_ply"', draw_source) self.assertIn('"gs_model" if settings.export_format', draw_source) self.assertIn("model_row.prop(settings, model_property)", draw_source) self.assertIn('model_row.label(text="", icon="CHECKMARK")', draw_source) self.assertIn('model_row.label(text="", icon="ERROR")', draw_source) self.assertNotIn('text="Ready"', draw_source) self.assertNotIn('text="Missing"', draw_source) self.assertNotIn("License:", draw_source) self.assertIn('operator.job_type = "RECONSTRUCT"', draw_source) def test_runtime_models_panel_assigns_each_download_target(self): draw_source = inspect.getsource( self.addon.OMOO_DA3_PT_runtime_models.draw ) model_source = inspect.getsource( self.addon.OMOO_DA3_PT_runtime_models._draw_model ) self.assertIn("for model in MODELS.values()", draw_source) self.assertIn("operator.model_key = model.key", model_source) self.assertNotIn("Isolated Runtime ready", draw_source) self.assertNotIn("License:", model_source) self.assertEqual( self.addon.OMOO_DA3_PT_runtime_models.bl_label, "Setup", ) 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( omoo_da3_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": "25%"}], ) self.assertEqual( operator_calls, [("omoo_da3.cancel_job", {"text": "", "icon": "X"})], ) def test_ui_redraw_targets_status_bar_and_view_3d_areas(self): redrawn = [] areas = ( SimpleNamespace( type="STATUSBAR", tag_redraw=lambda: redrawn.append("status"), ), SimpleNamespace( type="VIEW_3D", tag_redraw=lambda: redrawn.append("view"), ), ) context = SimpleNamespace( window_manager=SimpleNamespace( windows=( SimpleNamespace(screen=SimpleNamespace(areas=areas)), ), ), ) self.addon.status_bar.redraw_ui(context) self.assertEqual(redrawn, ["status", "view"]) def test_forced_ui_redraw_swaps_window_immediately(self): calls = [] self.fake_bpy.ops = SimpleNamespace( wm=SimpleNamespace( redraw_timer=lambda **options: calls.append(options), ), ) context = SimpleNamespace( window=SimpleNamespace(), window_manager=SimpleNamespace(windows=()), ) self.addon.status_bar.redraw_ui(context, force=True) self.assertEqual( calls, [{"type": "DRAW_WIN_SWAP", "iterations": 1}], )