import platform import sys import bpy from .job_runtime import runtime from . import model_catalog as shared_model_catalog BEN2_MODEL_KEY = "BEN2_BASE" DEFAULT_DA3_MODEL_KEY = "BASE" DEFAULT_MOGE2_MODEL_KEY = "MOGE2_VITS_NORMAL" REQUIRED_MODEL_KEYS = (DEFAULT_MOGE2_MODEL_KEY, BEN2_MODEL_KEY) DEVICE_LABELS = { "cuda": ("CUDA", "NVIDIA CUDA"), "directml": ("DirectML", "Windows GPU (DirectML)"), "mps": ("MPS", "Apple Metal (MPS)"), "cpu": ("CPU", "CPU"), } def supported_devices(): devices = runtime.environment_manifest().get("devices") if isinstance(devices, list) and devices: return [str(device).lower() for device in devices] if sys.platform == "win32": return ["directml", "cpu"] if sys.platform == "darwin" and platform.machine().lower() in { "arm64", "aarch64", }: return ["mps", "cpu"] return ["cpu"] def device_enum_items(_owner, _context): return [ (name.upper(), *DEVICE_LABELS[name], index) for index, name in enumerate(supported_devices()) ] def models_status(refresh=False): status = runtime.server_status().get("resources", {}).get("model_manager", {}) return { "catalog": tuple( shared_model_catalog.model_record( model, _model_files_ready(model), ) for model in shared_model_catalog.DOWNLOADABLE_MODELS.values() ), "loaded": status.get("loaded", {}), } def _model_files_ready(model): directory = runtime.storage_root() / "models" / model.directory_name return all( any(directory.glob(pattern)) for pattern in model.ready_patterns ) def model_ready(model_key): return any( model.get("key") == model_key and model.get("ready") for model in models_status().get("catalog", ()) ) def required_models_ready(): return all(model_ready(model_key) for model_key in REQUIRED_MODEL_KEYS) def model_catalog(): return models_status().get("catalog", ()) def model_record(model_key): return next( (model for model in model_catalog() if model.get("key") == model_key), None, ) def _model_items(models): return [ ( model["key"], model["label"] if model.get("ready") else f"{model['label']} (Missing)", f"{model['description']}; license: {model['license']}", index, ) for index, model in enumerate(models) ] def model_enum_items(_owner, _context): return _model_items( model for model in model_catalog() if model.get("family") == "da3" and model.get("enabled") ) def gs_model_enum_items(_owner, _context): return _model_items( model for model in model_catalog() if model.get("family") == "da3" and model.get("enabled") and model.get("supports_gs") ) def moge2_model_enum_items(_owner, _context): return _model_items( model for model in model_catalog() if model.get("family") == "moge2" ) def depth_plane_model_enum_items(_owner, _context): models = [ model for model in model_catalog() if model.get("family") in {"moge2", "da3"} and model.get("enabled") ] models.sort(key=lambda model: model.get("key") != DEFAULT_MOGE2_MODEL_KEY) items = _model_items(models) items.append( ("ORIGINAL", "Original", "Use the original image as depth", len(items)) ) return items def surface_model_enum_items(_owner, _context): models = [ model for model in model_catalog() if model.get("family") in {"moge2", "da3"} and model.get("enabled") ] models.sort(key=lambda model: model.get("key") != DEFAULT_MOGE2_MODEL_KEY) return _model_items(models) class SnapFormSettings(bpy.types.PropertyGroup): input_path: bpy.props.StringProperty( name="Image or Video", description="A single image or video file used for reconstruction", subtype="FILE_PATH", ) background_input_path: bpy.props.StringProperty( name="Image", description="A single image used to test BEN2 background removal", subtype="FILE_PATH", ) video_frames: bpy.props.IntProperty( name="Extract Frames", description="Maximum number of consecutive frames extracted from the start of a video or frame sequence", default=16, min=1, soft_max=64, ) model: bpy.props.EnumProperty( name="Model", description="Depth Anything 3 model", items=model_enum_items, ) gs_model: bpy.props.EnumProperty( name="Model", description="Depth Anything 3 model with a Gaussian head", items=gs_model_enum_items, ) device: bpy.props.EnumProperty( name="Debug Device", description=( "Inference device used by Debug tasks only; production tasks use " "the Device setting in the Add-on Preferences" ), items=device_enum_items, default=0, ) process_res: bpy.props.IntProperty( name="Process Res", description="DA3 base processing resolution", default=504, min=1, soft_max=2048, ) process_res_method: bpy.props.EnumProperty( name="Process Res Method", description="DA3 image resize method", items=( ( "upper_bound_resize", "Upper Bound Resize", "Make Process Res the longer side", ), ( "lower_bound_resize", "Lower Bound Resize", "Make Process Res the shorter side", ), ), default="upper_bound_resize", ) moge_resolution_level: bpy.props.IntProperty( name="Resolution Level", description="MoGe-2 inference resolution level; higher is finer and slower", default=5, min=0, max=9, ) moge_model: bpy.props.EnumProperty( name="Model", description="MoGe-2 model used for depth estimation", items=moge2_model_enum_items, default=0, ) moge_export_format: bpy.props.EnumProperty( name="Output Format", description="Additional MoGe-2 debug output generated with results.npz", items=( ("maps", "Maps", "Export depth, normal, and mask previews"), ("glb", "Textured Surface (GLB)", "Export the official-style textured mesh"), ("ply", "Point Cloud (PLY)", "Export the official-style colored point cloud"), ), default="maps", ) conf_thresh_percentile: bpy.props.FloatProperty( name="Confidence Threshold", description="Remove the lowest-confidence percentile of points or surface faces", default=40.0, min=0.0, max=100.0, subtype="PERCENTAGE", ) num_max_points: bpy.props.IntProperty( name="Num Max Points", description="DA3 GLB maximum point count", default=1_000_000, min=1, soft_max=5_000_000, ) show_cameras: bpy.props.BoolProperty( name="Show Cameras", description="Include DA3 camera wireframes in the GLB export", default=True, ) ref_view_strategy: bpy.props.EnumProperty( name="Ref View Strategy", description="DA3 reference view selection strategy", items=( ("first", "First", "Always use the first view"), ("middle", "Middle", "Use the middle view"), ( "saddle_balanced", "Saddle Balanced", "Select a view with balanced feature metrics", ), ( "saddle_sim_range", "Saddle Sim Range", "Select the view with the largest similarity range", ), ), default="saddle_balanced", ) export_format: bpy.props.EnumProperty( name="Output Type", description="Result generated by the DA3 debug task", items=( ( "glb", "Point Cloud (GLB)", "Generate a colored point cloud and optional cameras", ), ( "gs_ply", "Gaussian Splats (PLY)", "Generate a 3D Gaussian Splatting PLY", ), ( "depth_vis", "Depth Preview (Images)", "Generate image and colored-depth preview pairs", ), ( "mini_npz", "Depth Data (NPZ)", "Generate compact depth, confidence, and camera data", ), ( "mesh", "Textured Surface (GLB)", "Generate one filtered textured depth surface per input image", ), ), default="glb", ) mesh_filter_edges: bpy.props.BoolProperty( name="Filter Depth Edges", description="Remove faces around sharp depth transitions", default=True, ) mesh_max_edge_length: bpy.props.FloatProperty( name="Max Edge Length", description=( "Remove faces with an edge longer than this scene distance; " "zero disables the test" ), default=0.1, min=0.0, soft_max=2.0, ) gs_views_interval: bpy.props.IntProperty( name="GS Views Interval", description="Export 3D Gaussians every N input views", default=1, min=1, )