@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,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)}%")