@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,537 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Connection manager for remote CUDA Multi-View Stereo processing.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to discover, connect to, and manage
|
|
5
|
+
connections with remote CUDA MVS servers within the LAN.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
import threading
|
|
13
|
+
from typing import Dict, List, Optional, Any, Union, Callable
|
|
14
|
+
import socket
|
|
15
|
+
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
|
|
16
|
+
|
|
17
|
+
from src.remote.cuda_mvs_client import CUDAMVSClient
|
|
18
|
+
|
|
19
|
+
# Configure logging
|
|
20
|
+
logging.basicConfig(level=logging.INFO)
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
class CUDAMVSConnectionManager:
|
|
24
|
+
"""
|
|
25
|
+
Connection manager for remote CUDA MVS servers.
|
|
26
|
+
|
|
27
|
+
This class handles:
|
|
28
|
+
1. Discovering available CUDA MVS servers on the LAN
|
|
29
|
+
2. Managing connections to multiple servers
|
|
30
|
+
3. Load balancing across available servers
|
|
31
|
+
4. Monitoring server health and status
|
|
32
|
+
5. Automatic failover if a server becomes unavailable
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
api_key: Optional[str] = None,
|
|
38
|
+
discovery_port: int = 8765,
|
|
39
|
+
connection_timeout: int = 10,
|
|
40
|
+
health_check_interval: int = 60,
|
|
41
|
+
auto_discover: bool = True
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Initialize the connection manager.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
api_key: API key for authentication (if required)
|
|
48
|
+
discovery_port: Port used for server discovery
|
|
49
|
+
connection_timeout: Timeout for server connections in seconds
|
|
50
|
+
health_check_interval: Interval for health checks in seconds
|
|
51
|
+
auto_discover: Whether to automatically discover servers on startup
|
|
52
|
+
"""
|
|
53
|
+
self.api_key = api_key
|
|
54
|
+
self.discovery_port = discovery_port
|
|
55
|
+
self.connection_timeout = connection_timeout
|
|
56
|
+
self.health_check_interval = health_check_interval
|
|
57
|
+
|
|
58
|
+
# Server tracking
|
|
59
|
+
self.servers: Dict[str, Dict[str, Any]] = {}
|
|
60
|
+
self.clients: Dict[str, CUDAMVSClient] = {}
|
|
61
|
+
self.server_lock = threading.RLock()
|
|
62
|
+
|
|
63
|
+
# Health check thread
|
|
64
|
+
self.health_check_thread = None
|
|
65
|
+
self.health_check_stop_event = threading.Event()
|
|
66
|
+
|
|
67
|
+
# Discovery
|
|
68
|
+
self.zeroconf = None
|
|
69
|
+
self.browser = None
|
|
70
|
+
|
|
71
|
+
# Start discovery if enabled
|
|
72
|
+
if auto_discover:
|
|
73
|
+
self.start_discovery()
|
|
74
|
+
|
|
75
|
+
# Start health check thread
|
|
76
|
+
self.start_health_check()
|
|
77
|
+
|
|
78
|
+
def start_discovery(self):
|
|
79
|
+
"""
|
|
80
|
+
Start discovering CUDA MVS servers on the LAN.
|
|
81
|
+
"""
|
|
82
|
+
if self.zeroconf is not None:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
self.zeroconf = Zeroconf()
|
|
87
|
+
listener = CUDAMVSServiceListener(self)
|
|
88
|
+
self.browser = ServiceBrowser(self.zeroconf, "_cudamvs._tcp.local.", listener)
|
|
89
|
+
logger.info("Started CUDA MVS server discovery")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Error starting discovery: {e}")
|
|
92
|
+
|
|
93
|
+
def stop_discovery(self):
|
|
94
|
+
"""
|
|
95
|
+
Stop discovering CUDA MVS servers.
|
|
96
|
+
"""
|
|
97
|
+
if self.zeroconf is not None:
|
|
98
|
+
try:
|
|
99
|
+
self.zeroconf.close()
|
|
100
|
+
self.zeroconf = None
|
|
101
|
+
self.browser = None
|
|
102
|
+
logger.info("Stopped CUDA MVS server discovery")
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"Error stopping discovery: {e}")
|
|
105
|
+
|
|
106
|
+
def start_health_check(self):
|
|
107
|
+
"""
|
|
108
|
+
Start the health check thread.
|
|
109
|
+
"""
|
|
110
|
+
if self.health_check_thread is not None and self.health_check_thread.is_alive():
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
self.health_check_stop_event.clear()
|
|
114
|
+
self.health_check_thread = threading.Thread(
|
|
115
|
+
target=self._health_check_loop,
|
|
116
|
+
daemon=True
|
|
117
|
+
)
|
|
118
|
+
self.health_check_thread.start()
|
|
119
|
+
logger.info("Started health check thread")
|
|
120
|
+
|
|
121
|
+
def stop_health_check(self):
|
|
122
|
+
"""
|
|
123
|
+
Stop the health check thread.
|
|
124
|
+
"""
|
|
125
|
+
if self.health_check_thread is not None:
|
|
126
|
+
self.health_check_stop_event.set()
|
|
127
|
+
self.health_check_thread.join(timeout=5)
|
|
128
|
+
self.health_check_thread = None
|
|
129
|
+
logger.info("Stopped health check thread")
|
|
130
|
+
|
|
131
|
+
def _health_check_loop(self):
|
|
132
|
+
"""
|
|
133
|
+
Health check loop that runs in a separate thread.
|
|
134
|
+
"""
|
|
135
|
+
while not self.health_check_stop_event.is_set():
|
|
136
|
+
try:
|
|
137
|
+
self.check_all_servers()
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"Error in health check: {e}")
|
|
140
|
+
|
|
141
|
+
# Wait for the next check interval or until stopped
|
|
142
|
+
self.health_check_stop_event.wait(self.health_check_interval)
|
|
143
|
+
|
|
144
|
+
def add_server(self, server_info: Dict[str, Any]):
|
|
145
|
+
"""
|
|
146
|
+
Add a server to the manager.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
server_info: Dictionary with server information
|
|
150
|
+
"""
|
|
151
|
+
server_id = server_info.get("server_id")
|
|
152
|
+
if not server_id:
|
|
153
|
+
logger.error("Cannot add server without server_id")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
with self.server_lock:
|
|
157
|
+
# Check if server already exists
|
|
158
|
+
if server_id in self.servers:
|
|
159
|
+
# Update existing server info
|
|
160
|
+
self.servers[server_id].update(server_info)
|
|
161
|
+
logger.info(f"Updated server: {server_info.get('name')} at {server_info.get('url')}")
|
|
162
|
+
else:
|
|
163
|
+
# Add new server
|
|
164
|
+
self.servers[server_id] = server_info
|
|
165
|
+
|
|
166
|
+
# Create client for the server
|
|
167
|
+
self.clients[server_id] = CUDAMVSClient(
|
|
168
|
+
server_url=server_info.get("url"),
|
|
169
|
+
api_key=self.api_key,
|
|
170
|
+
connection_timeout=self.connection_timeout
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
logger.info(f"Added server: {server_info.get('name')} at {server_info.get('url')}")
|
|
174
|
+
|
|
175
|
+
def remove_server(self, server_id: str):
|
|
176
|
+
"""
|
|
177
|
+
Remove a server from the manager.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
server_id: ID of the server to remove
|
|
181
|
+
"""
|
|
182
|
+
with self.server_lock:
|
|
183
|
+
if server_id in self.servers:
|
|
184
|
+
server_info = self.servers.pop(server_id)
|
|
185
|
+
if server_id in self.clients:
|
|
186
|
+
del self.clients[server_id]
|
|
187
|
+
logger.info(f"Removed server: {server_info.get('name')} at {server_info.get('url')}")
|
|
188
|
+
|
|
189
|
+
def get_servers(self) -> List[Dict[str, Any]]:
|
|
190
|
+
"""
|
|
191
|
+
Get a list of all servers.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
List of dictionaries with server information
|
|
195
|
+
"""
|
|
196
|
+
with self.server_lock:
|
|
197
|
+
return list(self.servers.values())
|
|
198
|
+
|
|
199
|
+
def get_server(self, server_id: str) -> Optional[Dict[str, Any]]:
|
|
200
|
+
"""
|
|
201
|
+
Get information about a specific server.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
server_id: ID of the server
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Dictionary with server information or None if not found
|
|
208
|
+
"""
|
|
209
|
+
with self.server_lock:
|
|
210
|
+
return self.servers.get(server_id)
|
|
211
|
+
|
|
212
|
+
def get_client(self, server_id: str) -> Optional[CUDAMVSClient]:
|
|
213
|
+
"""
|
|
214
|
+
Get the client for a specific server.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
server_id: ID of the server
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
CUDAMVSClient instance or None if not found
|
|
221
|
+
"""
|
|
222
|
+
with self.server_lock:
|
|
223
|
+
return self.clients.get(server_id)
|
|
224
|
+
|
|
225
|
+
def get_best_server(self) -> Optional[str]:
|
|
226
|
+
"""
|
|
227
|
+
Get the ID of the best server to use based on availability and load.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
Server ID or None if no servers are available
|
|
231
|
+
"""
|
|
232
|
+
with self.server_lock:
|
|
233
|
+
available_servers = [
|
|
234
|
+
server_id for server_id, server in self.servers.items()
|
|
235
|
+
if server.get("status") == "available"
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
if not available_servers:
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
# For now, just return the first available server
|
|
242
|
+
# In a more advanced implementation, this would consider
|
|
243
|
+
# server load, capabilities, latency, etc.
|
|
244
|
+
return available_servers[0]
|
|
245
|
+
|
|
246
|
+
def check_server(self, server_id: str) -> Dict[str, Any]:
|
|
247
|
+
"""
|
|
248
|
+
Check the status of a specific server.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
server_id: ID of the server to check
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
Dictionary with server status information
|
|
255
|
+
"""
|
|
256
|
+
client = self.get_client(server_id)
|
|
257
|
+
if not client:
|
|
258
|
+
return {"status": "error", "message": f"Server {server_id} not found"}
|
|
259
|
+
|
|
260
|
+
# Test connection
|
|
261
|
+
result = client.test_connection()
|
|
262
|
+
|
|
263
|
+
with self.server_lock:
|
|
264
|
+
if server_id in self.servers:
|
|
265
|
+
# Update server status
|
|
266
|
+
if result["status"] == "success":
|
|
267
|
+
self.servers[server_id]["status"] = "available"
|
|
268
|
+
self.servers[server_id]["last_check"] = time.time()
|
|
269
|
+
self.servers[server_id]["latency_ms"] = result.get("latency_ms")
|
|
270
|
+
|
|
271
|
+
# Update capabilities if available
|
|
272
|
+
if "server_info" in result and "capabilities" in result["server_info"]:
|
|
273
|
+
self.servers[server_id]["capabilities"] = result["server_info"]["capabilities"]
|
|
274
|
+
else:
|
|
275
|
+
self.servers[server_id]["status"] = "unavailable"
|
|
276
|
+
self.servers[server_id]["last_check"] = time.time()
|
|
277
|
+
self.servers[server_id]["error"] = result.get("message")
|
|
278
|
+
|
|
279
|
+
return result
|
|
280
|
+
|
|
281
|
+
def check_all_servers(self) -> Dict[str, Dict[str, Any]]:
|
|
282
|
+
"""
|
|
283
|
+
Check the status of all servers.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Dictionary mapping server IDs to status information
|
|
287
|
+
"""
|
|
288
|
+
results = {}
|
|
289
|
+
|
|
290
|
+
with self.server_lock:
|
|
291
|
+
server_ids = list(self.servers.keys())
|
|
292
|
+
|
|
293
|
+
for server_id in server_ids:
|
|
294
|
+
results[server_id] = self.check_server(server_id)
|
|
295
|
+
|
|
296
|
+
return results
|
|
297
|
+
|
|
298
|
+
def discover_servers(self) -> List[Dict[str, Any]]:
|
|
299
|
+
"""
|
|
300
|
+
Manually discover CUDA MVS servers on the LAN.
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
List of dictionaries containing server information
|
|
304
|
+
"""
|
|
305
|
+
# Create a temporary client to discover servers
|
|
306
|
+
client = CUDAMVSClient(
|
|
307
|
+
api_key=self.api_key,
|
|
308
|
+
discovery_port=self.discovery_port,
|
|
309
|
+
connection_timeout=self.connection_timeout
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
discovered_servers = client.discover_servers()
|
|
313
|
+
|
|
314
|
+
# Add discovered servers
|
|
315
|
+
for server_info in discovered_servers:
|
|
316
|
+
self.add_server(server_info)
|
|
317
|
+
|
|
318
|
+
return discovered_servers
|
|
319
|
+
|
|
320
|
+
def upload_images_to_best_server(self, image_paths: List[str]) -> Dict[str, Any]:
|
|
321
|
+
"""
|
|
322
|
+
Upload images to the best available server.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
image_paths: List of paths to images to upload
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Dictionary with upload status and job information
|
|
329
|
+
"""
|
|
330
|
+
server_id = self.get_best_server()
|
|
331
|
+
if not server_id:
|
|
332
|
+
return {"status": "error", "message": "No available servers"}
|
|
333
|
+
|
|
334
|
+
client = self.get_client(server_id)
|
|
335
|
+
if not client:
|
|
336
|
+
return {"status": "error", "message": f"Client for server {server_id} not found"}
|
|
337
|
+
|
|
338
|
+
# Upload images
|
|
339
|
+
result = client.upload_images(image_paths)
|
|
340
|
+
|
|
341
|
+
# Add server information to result
|
|
342
|
+
result["server_id"] = server_id
|
|
343
|
+
result["server_name"] = self.servers[server_id].get("name")
|
|
344
|
+
|
|
345
|
+
return result
|
|
346
|
+
|
|
347
|
+
def generate_model_from_images(
|
|
348
|
+
self,
|
|
349
|
+
image_paths: List[str],
|
|
350
|
+
output_format: str = "obj",
|
|
351
|
+
wait_for_completion: bool = True,
|
|
352
|
+
poll_interval: int = 5,
|
|
353
|
+
server_id: Optional[str] = None
|
|
354
|
+
) -> Dict[str, Any]:
|
|
355
|
+
"""
|
|
356
|
+
Generate a 3D model from images using the best available server.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
image_paths: List of paths to images
|
|
360
|
+
output_format: Format of the output model
|
|
361
|
+
wait_for_completion: Whether to wait for job completion
|
|
362
|
+
poll_interval: Interval in seconds to poll for job status
|
|
363
|
+
server_id: ID of the server to use (uses best server if None)
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Dictionary with job status and model information if completed
|
|
367
|
+
"""
|
|
368
|
+
# Get server to use
|
|
369
|
+
if server_id is None:
|
|
370
|
+
server_id = self.get_best_server()
|
|
371
|
+
if not server_id:
|
|
372
|
+
return {"status": "error", "message": "No available servers"}
|
|
373
|
+
|
|
374
|
+
client = self.get_client(server_id)
|
|
375
|
+
if not client:
|
|
376
|
+
return {"status": "error", "message": f"Client for server {server_id} not found"}
|
|
377
|
+
|
|
378
|
+
# Generate model
|
|
379
|
+
result = client.generate_model_from_images(
|
|
380
|
+
image_paths=image_paths,
|
|
381
|
+
output_format=output_format,
|
|
382
|
+
wait_for_completion=wait_for_completion,
|
|
383
|
+
poll_interval=poll_interval
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Add server information to result
|
|
387
|
+
result["server_id"] = server_id
|
|
388
|
+
result["server_name"] = self.servers[server_id].get("name")
|
|
389
|
+
|
|
390
|
+
return result
|
|
391
|
+
|
|
392
|
+
def get_job_status(self, job_id: str, server_id: str) -> Dict[str, Any]:
|
|
393
|
+
"""
|
|
394
|
+
Get the status of a job on a specific server.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
job_id: ID of the job to check
|
|
398
|
+
server_id: ID of the server
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
Dictionary with job status information
|
|
402
|
+
"""
|
|
403
|
+
client = self.get_client(server_id)
|
|
404
|
+
if not client:
|
|
405
|
+
return {"status": "error", "message": f"Client for server {server_id} not found"}
|
|
406
|
+
|
|
407
|
+
return client.get_job_status(job_id)
|
|
408
|
+
|
|
409
|
+
def download_model(self, job_id: str, server_id: str, output_format: str = "obj") -> Dict[str, Any]:
|
|
410
|
+
"""
|
|
411
|
+
Download a processed 3D model from a specific server.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
job_id: ID of the job to download
|
|
415
|
+
server_id: ID of the server
|
|
416
|
+
output_format: Format of the model to download
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
Dictionary with download status and local file path
|
|
420
|
+
"""
|
|
421
|
+
client = self.get_client(server_id)
|
|
422
|
+
if not client:
|
|
423
|
+
return {"status": "error", "message": f"Client for server {server_id} not found"}
|
|
424
|
+
|
|
425
|
+
return client.download_model(job_id, output_format)
|
|
426
|
+
|
|
427
|
+
def cancel_job(self, job_id: str, server_id: str) -> Dict[str, Any]:
|
|
428
|
+
"""
|
|
429
|
+
Cancel a running job on a specific server.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
job_id: ID of the job to cancel
|
|
433
|
+
server_id: ID of the server
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
Dictionary with cancellation status
|
|
437
|
+
"""
|
|
438
|
+
client = self.get_client(server_id)
|
|
439
|
+
if not client:
|
|
440
|
+
return {"status": "error", "message": f"Client for server {server_id} not found"}
|
|
441
|
+
|
|
442
|
+
return client.cancel_job(job_id)
|
|
443
|
+
|
|
444
|
+
def cleanup(self):
|
|
445
|
+
"""
|
|
446
|
+
Clean up resources.
|
|
447
|
+
"""
|
|
448
|
+
self.stop_health_check()
|
|
449
|
+
self.stop_discovery()
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
class CUDAMVSServiceListener(ServiceListener):
|
|
453
|
+
"""
|
|
454
|
+
Zeroconf service listener for CUDA MVS servers.
|
|
455
|
+
"""
|
|
456
|
+
|
|
457
|
+
def __init__(self, connection_manager: CUDAMVSConnectionManager):
|
|
458
|
+
"""
|
|
459
|
+
Initialize the service listener.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
connection_manager: Connection manager to update with discovered servers
|
|
463
|
+
"""
|
|
464
|
+
self.connection_manager = connection_manager
|
|
465
|
+
|
|
466
|
+
def add_service(self, zc: Zeroconf, type_: str, name: str):
|
|
467
|
+
"""
|
|
468
|
+
Called when a service is discovered.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
zc: Zeroconf instance
|
|
472
|
+
type_: Service type
|
|
473
|
+
name: Service name
|
|
474
|
+
"""
|
|
475
|
+
info = zc.get_service_info(type_, name)
|
|
476
|
+
if info:
|
|
477
|
+
try:
|
|
478
|
+
# Extract server information
|
|
479
|
+
server_id = name.split('.')[0]
|
|
480
|
+
server_name = info.properties.get(b'name', b'Unknown').decode('utf-8')
|
|
481
|
+
|
|
482
|
+
# Get server URL
|
|
483
|
+
addresses = info.parsed_addresses()
|
|
484
|
+
if not addresses:
|
|
485
|
+
return
|
|
486
|
+
|
|
487
|
+
server_url = f"http://{addresses[0]}:{info.port}"
|
|
488
|
+
|
|
489
|
+
# Parse capabilities
|
|
490
|
+
capabilities = {}
|
|
491
|
+
if b'capabilities' in info.properties:
|
|
492
|
+
try:
|
|
493
|
+
capabilities = json.loads(info.properties[b'capabilities'].decode('utf-8'))
|
|
494
|
+
except json.JSONDecodeError:
|
|
495
|
+
pass
|
|
496
|
+
|
|
497
|
+
# Create server info
|
|
498
|
+
server_info = {
|
|
499
|
+
"server_id": server_id,
|
|
500
|
+
"name": server_name,
|
|
501
|
+
"url": server_url,
|
|
502
|
+
"capabilities": capabilities,
|
|
503
|
+
"status": "unknown",
|
|
504
|
+
"discovered_at": time.time()
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
# Add server to connection manager
|
|
508
|
+
self.connection_manager.add_server(server_info)
|
|
509
|
+
|
|
510
|
+
except Exception as e:
|
|
511
|
+
logger.error(f"Error processing discovered service: {e}")
|
|
512
|
+
|
|
513
|
+
def remove_service(self, zc: Zeroconf, type_: str, name: str):
|
|
514
|
+
"""
|
|
515
|
+
Called when a service is removed.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
zc: Zeroconf instance
|
|
519
|
+
type_: Service type
|
|
520
|
+
name: Service name
|
|
521
|
+
"""
|
|
522
|
+
try:
|
|
523
|
+
server_id = name.split('.')[0]
|
|
524
|
+
self.connection_manager.remove_server(server_id)
|
|
525
|
+
except Exception as e:
|
|
526
|
+
logger.error(f"Error removing service: {e}")
|
|
527
|
+
|
|
528
|
+
def update_service(self, zc: Zeroconf, type_: str, name: str):
|
|
529
|
+
"""
|
|
530
|
+
Called when a service is updated.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
zc: Zeroconf instance
|
|
534
|
+
type_: Service type
|
|
535
|
+
name: Service name
|
|
536
|
+
"""
|
|
537
|
+
self.add_service(zc, type_, name)
|