@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.
Files changed (192) hide show
  1. package/README.md +116 -0
  2. package/config/mcp/antigravity.json +22 -0
  3. package/config/mcp/claude.json +22 -0
  4. package/config/mcp/codex.toml +25 -0
  5. package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
  6. package/docs/review_documentation.md +121 -0
  7. package/installer.js +358 -0
  8. package/mcp_servers/kicad-mcp-server/.env.example +22 -0
  9. package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
  10. package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
  11. package/mcp_servers/kicad-mcp-server/README.md +316 -0
  12. package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
  13. package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
  14. package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
  15. package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
  16. package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
  17. package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
  18. package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
  19. package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
  20. package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
  21. package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
  22. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
  23. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
  24. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
  25. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
  26. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
  27. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
  28. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
  29. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
  30. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
  31. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
  32. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
  33. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
  34. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
  35. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
  36. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
  37. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
  38. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
  39. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
  40. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
  41. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
  42. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
  43. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
  44. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
  45. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
  46. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
  47. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
  48. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
  49. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
  50. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
  51. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
  52. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
  53. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
  54. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
  55. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
  56. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
  57. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
  58. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
  59. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
  60. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
  61. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
  62. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
  63. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
  64. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
  65. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
  66. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
  67. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
  68. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
  69. package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
  70. package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
  71. package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
  72. package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
  73. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
  74. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
  75. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
  76. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
  77. package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
  78. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
  79. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
  80. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
  81. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
  82. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
  83. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
  84. package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
  85. package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
  86. package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
  87. package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
  88. package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
  89. package/mcp_servers/openscad-mcp-server/README.md +154 -0
  90. package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
  91. package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
  92. package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
  93. package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
  94. package/mcp_servers/openscad-mcp-server/glama.json +4 -0
  95. package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
  96. package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
  97. package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
  98. package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
  99. package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
  100. package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
  101. package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
  102. package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
  103. package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
  104. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
  105. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
  106. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
  107. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
  108. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
  109. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
  110. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
  111. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
  112. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
  113. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
  114. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
  115. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
  116. package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
  117. package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
  118. package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
  119. package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
  120. package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
  121. package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
  122. package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
  123. package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
  124. package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
  125. package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
  126. package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
  127. package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
  128. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
  129. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
  130. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
  131. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
  132. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
  133. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
  134. package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
  135. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
  136. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
  137. package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
  138. package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
  139. package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
  140. package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
  141. package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
  142. package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
  143. package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
  144. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
  145. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
  146. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
  147. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
  148. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
  149. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
  150. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
  151. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
  152. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
  153. package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
  154. package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
  155. package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
  156. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
  157. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
  158. package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
  159. package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
  160. package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
  161. package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
  162. package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
  163. package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
  164. package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
  165. package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
  166. package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
  167. package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
  168. package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
  169. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
  170. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
  171. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
  172. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
  173. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
  174. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
  175. package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
  176. package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
  177. package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
  178. package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
  179. package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
  180. package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
  181. package/package.json +44 -0
  182. package/scripts/install_mcps.sh +86 -0
  183. package/scripts/openscad_wrapper.sh +62 -0
  184. package/scripts/run_kicad_mcp.sh +25 -0
  185. package/scripts/run_openscad_mcp.sh +41 -0
  186. package/scripts/test_mcp_connection.py +626 -0
  187. package/scripts/test_mcp_connection.sh +168 -0
  188. package/skills/code-first-hardware-design/SKILL.md +260 -0
  189. package/skills/pcb-constraint-definition/SKILL.md +236 -0
  190. package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
  191. package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
  192. 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)