@hybridlabor-api/bdb-hardware-pcb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -0
- package/config/mcp/antigravity.json +22 -0
- package/config/mcp/claude.json +22 -0
- package/config/mcp/codex.toml +25 -0
- package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
- package/docs/review_documentation.md +121 -0
- package/installer.js +358 -0
- package/mcp_servers/kicad-mcp-server/.env.example +22 -0
- package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
- package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
- package/mcp_servers/kicad-mcp-server/README.md +316 -0
- package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
- package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
- package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
- package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
- package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
- package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
- package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
- package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
- package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
- package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
- package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
- package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
- package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
- package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
- package/mcp_servers/openscad-mcp-server/README.md +154 -0
- package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
- package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
- package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
- package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
- package/mcp_servers/openscad-mcp-server/glama.json +4 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
- package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
- package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
- package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
- package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
- package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
- package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
- package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
- package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
- package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
- package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
- package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
- package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
- package/package.json +44 -0
- package/scripts/install_mcps.sh +86 -0
- package/scripts/openscad_wrapper.sh +62 -0
- package/scripts/run_kicad_mcp.sh +25 -0
- package/scripts/run_openscad_mcp.sh +41 -0
- package/scripts/test_mcp_connection.py +626 -0
- package/scripts/test_mcp_connection.sh +168 -0
- package/skills/code-first-hardware-design/SKILL.md +260 -0
- package/skills/pcb-constraint-definition/SKILL.md +236 -0
- package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
- package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
- package/skills/schematic-datasheet-analysis/SKILL.md +202 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Client for remote CUDA Multi-View Stereo processing.
|
|
3
|
+
|
|
4
|
+
This module provides a client to connect to a remote CUDA MVS server
|
|
5
|
+
within the LAN for processing multi-view images into 3D models.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import requests
|
|
12
|
+
import base64
|
|
13
|
+
from typing import Dict, List, Optional, Any, Union
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
import uuid
|
|
16
|
+
|
|
17
|
+
# Configure logging
|
|
18
|
+
logging.basicConfig(level=logging.INFO)
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
class CUDAMVSClient:
|
|
22
|
+
"""
|
|
23
|
+
Client for connecting to a remote CUDA Multi-View Stereo server.
|
|
24
|
+
|
|
25
|
+
This client handles:
|
|
26
|
+
1. Discovering available CUDA MVS servers on the LAN
|
|
27
|
+
2. Uploading images to the server
|
|
28
|
+
3. Requesting 3D reconstruction
|
|
29
|
+
4. Downloading the resulting 3D models
|
|
30
|
+
5. Monitoring job status
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
server_url: Optional[str] = None,
|
|
36
|
+
api_key: Optional[str] = None,
|
|
37
|
+
output_dir: str = "output/models",
|
|
38
|
+
discovery_port: int = 8765,
|
|
39
|
+
connection_timeout: int = 10,
|
|
40
|
+
upload_chunk_size: int = 1024 * 1024, # 1MB chunks
|
|
41
|
+
):
|
|
42
|
+
"""
|
|
43
|
+
Initialize the CUDA MVS client.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
server_url: URL of the CUDA MVS server (if known)
|
|
47
|
+
api_key: API key for authentication (if required)
|
|
48
|
+
output_dir: Directory to save downloaded models
|
|
49
|
+
discovery_port: Port used for server discovery
|
|
50
|
+
connection_timeout: Timeout for server connections in seconds
|
|
51
|
+
upload_chunk_size: Chunk size for file uploads in bytes
|
|
52
|
+
"""
|
|
53
|
+
self.server_url = server_url
|
|
54
|
+
self.api_key = api_key
|
|
55
|
+
self.output_dir = output_dir
|
|
56
|
+
self.discovery_port = discovery_port
|
|
57
|
+
self.connection_timeout = connection_timeout
|
|
58
|
+
self.upload_chunk_size = upload_chunk_size
|
|
59
|
+
|
|
60
|
+
# Create output directory if it doesn't exist
|
|
61
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
# Initialize session for connection pooling
|
|
64
|
+
self.session = requests.Session()
|
|
65
|
+
if api_key:
|
|
66
|
+
self.session.headers.update({"Authorization": f"Bearer {api_key}"})
|
|
67
|
+
|
|
68
|
+
def discover_servers(self) -> List[Dict[str, Any]]:
|
|
69
|
+
"""
|
|
70
|
+
Discover CUDA MVS servers on the local network.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of dictionaries containing server information:
|
|
74
|
+
[
|
|
75
|
+
{
|
|
76
|
+
"server_id": "unique-server-id",
|
|
77
|
+
"name": "CUDA MVS Server 1",
|
|
78
|
+
"url": "http://192.168.1.100:8765",
|
|
79
|
+
"capabilities": {
|
|
80
|
+
"max_images": 50,
|
|
81
|
+
"max_resolution": 4096,
|
|
82
|
+
"supported_formats": ["jpg", "png"],
|
|
83
|
+
"gpu_info": "NVIDIA RTX 4090 24GB"
|
|
84
|
+
},
|
|
85
|
+
"status": "available"
|
|
86
|
+
},
|
|
87
|
+
...
|
|
88
|
+
]
|
|
89
|
+
"""
|
|
90
|
+
import socket
|
|
91
|
+
import json
|
|
92
|
+
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
|
|
93
|
+
|
|
94
|
+
discovered_servers = []
|
|
95
|
+
|
|
96
|
+
class CUDAMVSListener(ServiceListener):
|
|
97
|
+
def add_service(self, zc, type_, name):
|
|
98
|
+
info = zc.get_service_info(type_, name)
|
|
99
|
+
if info:
|
|
100
|
+
server_info = {
|
|
101
|
+
"server_id": name.split('.')[0],
|
|
102
|
+
"name": info.properties.get(b'name', b'Unknown').decode('utf-8'),
|
|
103
|
+
"url": f"http://{socket.inet_ntoa(info.addresses[0])}:{info.port}",
|
|
104
|
+
"capabilities": json.loads(info.properties.get(b'capabilities', b'{}').decode('utf-8')),
|
|
105
|
+
"status": "available"
|
|
106
|
+
}
|
|
107
|
+
discovered_servers.append(server_info)
|
|
108
|
+
logger.info(f"Discovered CUDA MVS server: {server_info['name']} at {server_info['url']}")
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
zeroconf = Zeroconf()
|
|
112
|
+
listener = CUDAMVSListener()
|
|
113
|
+
browser = ServiceBrowser(zeroconf, "_cudamvs._tcp.local.", listener)
|
|
114
|
+
|
|
115
|
+
# Wait for discovery (non-blocking in production code)
|
|
116
|
+
import time
|
|
117
|
+
time.sleep(2) # Give some time for discovery
|
|
118
|
+
|
|
119
|
+
zeroconf.close()
|
|
120
|
+
return discovered_servers
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.error(f"Error discovering CUDA MVS servers: {e}")
|
|
123
|
+
return []
|
|
124
|
+
|
|
125
|
+
def test_connection(self, server_url: Optional[str] = None) -> Dict[str, Any]:
|
|
126
|
+
"""
|
|
127
|
+
Test connection to a CUDA MVS server.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
server_url: URL of the server to test (uses self.server_url if None)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Dictionary with connection status and server information
|
|
134
|
+
"""
|
|
135
|
+
url = server_url or self.server_url
|
|
136
|
+
if not url:
|
|
137
|
+
return {"status": "error", "message": "No server URL provided"}
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
response = self.session.get(
|
|
141
|
+
f"{url}/api/status",
|
|
142
|
+
timeout=self.connection_timeout
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if response.status_code == 200:
|
|
146
|
+
return {
|
|
147
|
+
"status": "success",
|
|
148
|
+
"server_info": response.json(),
|
|
149
|
+
"latency_ms": response.elapsed.total_seconds() * 1000
|
|
150
|
+
}
|
|
151
|
+
else:
|
|
152
|
+
return {
|
|
153
|
+
"status": "error",
|
|
154
|
+
"message": f"Server returned status code {response.status_code}",
|
|
155
|
+
"details": response.text
|
|
156
|
+
}
|
|
157
|
+
except requests.exceptions.RequestException as e:
|
|
158
|
+
return {"status": "error", "message": f"Connection error: {str(e)}"}
|
|
159
|
+
|
|
160
|
+
def upload_images(self, image_paths: List[str]) -> Dict[str, Any]:
|
|
161
|
+
"""
|
|
162
|
+
Upload images to the CUDA MVS server.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
image_paths: List of paths to images to upload
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Dictionary with upload status and job information
|
|
169
|
+
"""
|
|
170
|
+
if not self.server_url:
|
|
171
|
+
return {"status": "error", "message": "No server URL configured"}
|
|
172
|
+
|
|
173
|
+
# Create a new job
|
|
174
|
+
try:
|
|
175
|
+
response = self.session.post(
|
|
176
|
+
f"{self.server_url}/api/jobs",
|
|
177
|
+
json={"num_images": len(image_paths)},
|
|
178
|
+
timeout=self.connection_timeout
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if response.status_code != 201:
|
|
182
|
+
return {
|
|
183
|
+
"status": "error",
|
|
184
|
+
"message": f"Failed to create job: {response.status_code}",
|
|
185
|
+
"details": response.text
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
job_info = response.json()
|
|
189
|
+
job_id = job_info["job_id"]
|
|
190
|
+
|
|
191
|
+
# Upload each image
|
|
192
|
+
for i, image_path in enumerate(image_paths):
|
|
193
|
+
# Check if file exists
|
|
194
|
+
if not os.path.exists(image_path):
|
|
195
|
+
return {
|
|
196
|
+
"status": "error",
|
|
197
|
+
"message": f"Image file not found: {image_path}"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
# Get file size for progress tracking
|
|
201
|
+
file_size = os.path.getsize(image_path)
|
|
202
|
+
|
|
203
|
+
# Prepare upload
|
|
204
|
+
with open(image_path, "rb") as f:
|
|
205
|
+
files = {
|
|
206
|
+
"file": (os.path.basename(image_path), f, "image/jpeg" if image_path.endswith(".jpg") else "image/png")
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
metadata = {
|
|
210
|
+
"image_index": i,
|
|
211
|
+
"total_images": len(image_paths),
|
|
212
|
+
"filename": os.path.basename(image_path)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
response = self.session.post(
|
|
216
|
+
f"{self.server_url}/api/jobs/{job_id}/images",
|
|
217
|
+
files=files,
|
|
218
|
+
data={"metadata": json.dumps(metadata)},
|
|
219
|
+
timeout=None # No timeout for uploads
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if response.status_code != 200:
|
|
223
|
+
return {
|
|
224
|
+
"status": "error",
|
|
225
|
+
"message": f"Failed to upload image {i+1}/{len(image_paths)}: {response.status_code}",
|
|
226
|
+
"details": response.text
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
logger.info(f"Uploaded image {i+1}/{len(image_paths)}: {os.path.basename(image_path)}")
|
|
230
|
+
|
|
231
|
+
# Start processing
|
|
232
|
+
response = self.session.post(
|
|
233
|
+
f"{self.server_url}/api/jobs/{job_id}/process",
|
|
234
|
+
timeout=self.connection_timeout
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if response.status_code != 202:
|
|
238
|
+
return {
|
|
239
|
+
"status": "error",
|
|
240
|
+
"message": f"Failed to start processing: {response.status_code}",
|
|
241
|
+
"details": response.text
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
"status": "success",
|
|
246
|
+
"job_id": job_id,
|
|
247
|
+
"message": f"Uploaded {len(image_paths)} images and started processing",
|
|
248
|
+
"job_url": f"{self.server_url}/api/jobs/{job_id}"
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
except requests.exceptions.RequestException as e:
|
|
252
|
+
return {"status": "error", "message": f"Upload error: {str(e)}"}
|
|
253
|
+
|
|
254
|
+
def get_job_status(self, job_id: str) -> Dict[str, Any]:
|
|
255
|
+
"""
|
|
256
|
+
Get the status of a CUDA MVS job.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
job_id: ID of the job to check
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Dictionary with job status information
|
|
263
|
+
"""
|
|
264
|
+
if not self.server_url:
|
|
265
|
+
return {"status": "error", "message": "No server URL configured"}
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
response = self.session.get(
|
|
269
|
+
f"{self.server_url}/api/jobs/{job_id}",
|
|
270
|
+
timeout=self.connection_timeout
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if response.status_code == 200:
|
|
274
|
+
return {
|
|
275
|
+
"status": "success",
|
|
276
|
+
"job_info": response.json()
|
|
277
|
+
}
|
|
278
|
+
else:
|
|
279
|
+
return {
|
|
280
|
+
"status": "error",
|
|
281
|
+
"message": f"Failed to get job status: {response.status_code}",
|
|
282
|
+
"details": response.text
|
|
283
|
+
}
|
|
284
|
+
except requests.exceptions.RequestException as e:
|
|
285
|
+
return {"status": "error", "message": f"Connection error: {str(e)}"}
|
|
286
|
+
|
|
287
|
+
def download_model(self, job_id: str, output_format: str = "obj") -> Dict[str, Any]:
|
|
288
|
+
"""
|
|
289
|
+
Download a processed 3D model from the CUDA MVS server.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
job_id: ID of the job to download
|
|
293
|
+
output_format: Format of the model to download (obj, ply, etc.)
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Dictionary with download status and local file path
|
|
297
|
+
"""
|
|
298
|
+
if not self.server_url:
|
|
299
|
+
return {"status": "error", "message": "No server URL configured"}
|
|
300
|
+
|
|
301
|
+
# Check job status first
|
|
302
|
+
status_result = self.get_job_status(job_id)
|
|
303
|
+
if status_result["status"] != "success":
|
|
304
|
+
return status_result
|
|
305
|
+
|
|
306
|
+
job_info = status_result["job_info"]
|
|
307
|
+
if job_info["status"] != "completed":
|
|
308
|
+
return {
|
|
309
|
+
"status": "error",
|
|
310
|
+
"message": f"Job is not completed yet. Current status: {job_info['status']}",
|
|
311
|
+
"job_info": job_info
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# Download the model
|
|
315
|
+
try:
|
|
316
|
+
response = self.session.get(
|
|
317
|
+
f"{self.server_url}/api/jobs/{job_id}/model?format={output_format}",
|
|
318
|
+
stream=True,
|
|
319
|
+
timeout=None # No timeout for downloads
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if response.status_code != 200:
|
|
323
|
+
return {
|
|
324
|
+
"status": "error",
|
|
325
|
+
"message": f"Failed to download model: {response.status_code}",
|
|
326
|
+
"details": response.text
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
# Create a unique filename
|
|
330
|
+
model_id = job_info.get("model_id", str(uuid.uuid4()))
|
|
331
|
+
output_path = os.path.join(self.output_dir, f"{model_id}.{output_format}")
|
|
332
|
+
|
|
333
|
+
# Save the file
|
|
334
|
+
with open(output_path, "wb") as f:
|
|
335
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
336
|
+
if chunk:
|
|
337
|
+
f.write(chunk)
|
|
338
|
+
|
|
339
|
+
logger.info(f"Downloaded model to {output_path}")
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
"status": "success",
|
|
343
|
+
"model_id": model_id,
|
|
344
|
+
"local_path": output_path,
|
|
345
|
+
"format": output_format,
|
|
346
|
+
"job_id": job_id
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
except requests.exceptions.RequestException as e:
|
|
350
|
+
return {"status": "error", "message": f"Download error: {str(e)}"}
|
|
351
|
+
|
|
352
|
+
def cancel_job(self, job_id: str) -> Dict[str, Any]:
|
|
353
|
+
"""
|
|
354
|
+
Cancel a running CUDA MVS job.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
job_id: ID of the job to cancel
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Dictionary with cancellation status
|
|
361
|
+
"""
|
|
362
|
+
if not self.server_url:
|
|
363
|
+
return {"status": "error", "message": "No server URL configured"}
|
|
364
|
+
|
|
365
|
+
try:
|
|
366
|
+
response = self.session.delete(
|
|
367
|
+
f"{self.server_url}/api/jobs/{job_id}",
|
|
368
|
+
timeout=self.connection_timeout
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if response.status_code == 200:
|
|
372
|
+
return {
|
|
373
|
+
"status": "success",
|
|
374
|
+
"message": "Job cancelled successfully"
|
|
375
|
+
}
|
|
376
|
+
else:
|
|
377
|
+
return {
|
|
378
|
+
"status": "error",
|
|
379
|
+
"message": f"Failed to cancel job: {response.status_code}",
|
|
380
|
+
"details": response.text
|
|
381
|
+
}
|
|
382
|
+
except requests.exceptions.RequestException as e:
|
|
383
|
+
return {"status": "error", "message": f"Connection error: {str(e)}"}
|
|
384
|
+
|
|
385
|
+
def generate_model_from_images(
|
|
386
|
+
self,
|
|
387
|
+
image_paths: List[str],
|
|
388
|
+
output_format: str = "obj",
|
|
389
|
+
wait_for_completion: bool = True,
|
|
390
|
+
poll_interval: int = 5
|
|
391
|
+
) -> Dict[str, Any]:
|
|
392
|
+
"""
|
|
393
|
+
Complete workflow to generate a 3D model from images.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
image_paths: List of paths to images
|
|
397
|
+
output_format: Format of the output model
|
|
398
|
+
wait_for_completion: Whether to wait for job completion
|
|
399
|
+
poll_interval: Interval in seconds to poll for job status
|
|
400
|
+
|
|
401
|
+
Returns:
|
|
402
|
+
Dictionary with job status and model information if completed
|
|
403
|
+
"""
|
|
404
|
+
# Upload images and start processing
|
|
405
|
+
upload_result = self.upload_images(image_paths)
|
|
406
|
+
if upload_result["status"] != "success":
|
|
407
|
+
return upload_result
|
|
408
|
+
|
|
409
|
+
job_id = upload_result["job_id"]
|
|
410
|
+
|
|
411
|
+
# If not waiting for completion, return the job info
|
|
412
|
+
if not wait_for_completion:
|
|
413
|
+
return upload_result
|
|
414
|
+
|
|
415
|
+
# Poll for job completion
|
|
416
|
+
import time
|
|
417
|
+
while True:
|
|
418
|
+
status_result = self.get_job_status(job_id)
|
|
419
|
+
if status_result["status"] != "success":
|
|
420
|
+
return status_result
|
|
421
|
+
|
|
422
|
+
job_info = status_result["job_info"]
|
|
423
|
+
if job_info["status"] == "completed":
|
|
424
|
+
# Download the model
|
|
425
|
+
return self.download_model(job_id, output_format)
|
|
426
|
+
elif job_info["status"] == "failed":
|
|
427
|
+
return {
|
|
428
|
+
"status": "error",
|
|
429
|
+
"message": "Job processing failed",
|
|
430
|
+
"job_info": job_info
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
# Wait before polling again
|
|
434
|
+
time.sleep(poll_interval)
|
|
435
|
+
logger.info(f"Job {job_id} status: {job_info['status']}, progress: {job_info.get('progress', 0)}%")
|