@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,471 @@
1
+ import os
2
+ import logging
3
+ import socket
4
+ import json
5
+ import time
6
+ import threading
7
+ from typing import Dict, List, Any, Optional, Callable
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class PrinterDiscovery:
12
+ """
13
+ Discovers 3D printers on the network and provides interfaces for direct printing.
14
+ """
15
+
16
+ def __init__(self):
17
+ """Initialize the printer discovery service."""
18
+ self.printers = {} # Dictionary of discovered printers
19
+ self.discovery_thread = None
20
+ self.discovery_stop_event = threading.Event()
21
+ self.discovery_callback = None
22
+
23
+ def start_discovery(self, callback: Optional[Callable[[Dict[str, Any]], None]] = None) -> None:
24
+ """
25
+ Start discovering 3D printers on the network.
26
+
27
+ Args:
28
+ callback: Optional callback function to call when a printer is discovered
29
+ """
30
+ if self.discovery_thread and self.discovery_thread.is_alive():
31
+ logger.warning("Printer discovery already running")
32
+ return
33
+
34
+ self.discovery_callback = callback
35
+ self.discovery_stop_event.clear()
36
+ self.discovery_thread = threading.Thread(target=self._discover_printers)
37
+ self.discovery_thread.daemon = True
38
+ self.discovery_thread.start()
39
+
40
+ logger.info("Started printer discovery")
41
+
42
+ def stop_discovery(self) -> None:
43
+ """Stop discovering 3D printers."""
44
+ if self.discovery_thread and self.discovery_thread.is_alive():
45
+ self.discovery_stop_event.set()
46
+ self.discovery_thread.join(timeout=2.0)
47
+ logger.info("Stopped printer discovery")
48
+ else:
49
+ logger.warning("Printer discovery not running")
50
+
51
+ def get_printers(self) -> Dict[str, Any]:
52
+ """
53
+ Get the list of discovered printers.
54
+
55
+ Returns:
56
+ Dictionary of printer information
57
+ """
58
+ return self.printers
59
+
60
+ def _discover_printers(self) -> None:
61
+ """Discover 3D printers on the network using various protocols."""
62
+ # This is a simplified implementation that simulates printer discovery
63
+ # In a real implementation, you would use protocols like mDNS, SNMP, or OctoPrint API
64
+
65
+ # Simulate discovering printers
66
+ while not self.discovery_stop_event.is_set():
67
+ try:
68
+ # Simulate network discovery
69
+ self._discover_octoprint_printers()
70
+ self._discover_prusa_printers()
71
+ self._discover_ultimaker_printers()
72
+
73
+ # Wait before next discovery cycle
74
+ time.sleep(10)
75
+ except Exception as e:
76
+ logger.error(f"Error in printer discovery: {str(e)}")
77
+ time.sleep(5)
78
+
79
+ def _discover_octoprint_printers(self) -> None:
80
+ """Discover OctoPrint servers on the network."""
81
+ # Simulate discovering OctoPrint servers
82
+ # In a real implementation, you would use mDNS to discover OctoPrint instances
83
+
84
+ # Simulate finding a printer
85
+ printer_id = "octoprint_1"
86
+ if printer_id not in self.printers:
87
+ printer_info = {
88
+ "id": printer_id,
89
+ "name": "OctoPrint Printer",
90
+ "type": "octoprint",
91
+ "address": "192.168.1.100",
92
+ "port": 80,
93
+ "api_key": None, # Would need to be provided by user
94
+ "status": "online",
95
+ "capabilities": ["print", "status", "cancel"]
96
+ }
97
+
98
+ self.printers[printer_id] = printer_info
99
+
100
+ if self.discovery_callback:
101
+ self.discovery_callback(printer_info)
102
+
103
+ logger.info(f"Discovered OctoPrint printer: {printer_info['name']}")
104
+
105
+ def _discover_prusa_printers(self) -> None:
106
+ """Discover Prusa printers on the network."""
107
+ # Simulate discovering Prusa printers
108
+
109
+ # Simulate finding a printer
110
+ printer_id = "prusa_1"
111
+ if printer_id not in self.printers:
112
+ printer_info = {
113
+ "id": printer_id,
114
+ "name": "Prusa MK3S",
115
+ "type": "prusa",
116
+ "address": "192.168.1.101",
117
+ "port": 80,
118
+ "status": "online",
119
+ "capabilities": ["print", "status"]
120
+ }
121
+
122
+ self.printers[printer_id] = printer_info
123
+
124
+ if self.discovery_callback:
125
+ self.discovery_callback(printer_info)
126
+
127
+ logger.info(f"Discovered Prusa printer: {printer_info['name']}")
128
+
129
+ def _discover_ultimaker_printers(self) -> None:
130
+ """Discover Ultimaker printers on the network."""
131
+ # Simulate discovering Ultimaker printers
132
+
133
+ # Simulate finding a printer
134
+ printer_id = "ultimaker_1"
135
+ if printer_id not in self.printers:
136
+ printer_info = {
137
+ "id": printer_id,
138
+ "name": "Ultimaker S5",
139
+ "type": "ultimaker",
140
+ "address": "192.168.1.102",
141
+ "port": 80,
142
+ "status": "online",
143
+ "capabilities": ["print", "status", "cancel"]
144
+ }
145
+
146
+ self.printers[printer_id] = printer_info
147
+
148
+ if self.discovery_callback:
149
+ self.discovery_callback(printer_info)
150
+
151
+ logger.info(f"Discovered Ultimaker printer: {printer_info['name']}")
152
+
153
+
154
+ class PrinterInterface:
155
+ """
156
+ Interface for communicating with 3D printers.
157
+ """
158
+
159
+ def __init__(self, printer_discovery: PrinterDiscovery):
160
+ """
161
+ Initialize the printer interface.
162
+
163
+ Args:
164
+ printer_discovery: Instance of PrinterDiscovery for finding printers
165
+ """
166
+ self.printer_discovery = printer_discovery
167
+ self.connected_printers = {} # Dictionary of connected printers
168
+
169
+ def connect_to_printer(self, printer_id: str, credentials: Optional[Dict[str, Any]] = None) -> bool:
170
+ """
171
+ Connect to a specific printer.
172
+
173
+ Args:
174
+ printer_id: ID of the printer to connect to
175
+ credentials: Optional credentials for authentication
176
+
177
+ Returns:
178
+ True if connection successful, False otherwise
179
+ """
180
+ printers = self.printer_discovery.get_printers()
181
+ if printer_id not in printers:
182
+ logger.error(f"Printer not found: {printer_id}")
183
+ return False
184
+
185
+ printer_info = printers[printer_id]
186
+
187
+ # Create appropriate printer client based on type
188
+ if printer_info["type"] == "octoprint":
189
+ client = OctoPrintClient(printer_info, credentials)
190
+ elif printer_info["type"] == "prusa":
191
+ client = PrusaClient(printer_info, credentials)
192
+ elif printer_info["type"] == "ultimaker":
193
+ client = UltimakerClient(printer_info, credentials)
194
+ else:
195
+ logger.error(f"Unsupported printer type: {printer_info['type']}")
196
+ return False
197
+
198
+ # Connect to the printer
199
+ if client.connect():
200
+ self.connected_printers[printer_id] = client
201
+ return True
202
+ else:
203
+ return False
204
+
205
+ def disconnect_from_printer(self, printer_id: str) -> bool:
206
+ """
207
+ Disconnect from a specific printer.
208
+
209
+ Args:
210
+ printer_id: ID of the printer to disconnect from
211
+
212
+ Returns:
213
+ True if disconnection successful, False otherwise
214
+ """
215
+ if printer_id not in self.connected_printers:
216
+ logger.error(f"Not connected to printer: {printer_id}")
217
+ return False
218
+
219
+ client = self.connected_printers[printer_id]
220
+ if client.disconnect():
221
+ del self.connected_printers[printer_id]
222
+ return True
223
+ else:
224
+ return False
225
+
226
+ def print_file(self, printer_id: str, file_path: str, print_settings: Optional[Dict[str, Any]] = None) -> bool:
227
+ """
228
+ Send a file to a printer for printing.
229
+
230
+ Args:
231
+ printer_id: ID of the printer to print on
232
+ file_path: Path to the STL file to print
233
+ print_settings: Optional print settings
234
+
235
+ Returns:
236
+ True if print job started successfully, False otherwise
237
+ """
238
+ if printer_id not in self.connected_printers:
239
+ logger.error(f"Not connected to printer: {printer_id}")
240
+ return False
241
+
242
+ client = self.connected_printers[printer_id]
243
+ return client.print_file(file_path, print_settings)
244
+
245
+ def get_printer_status(self, printer_id: str) -> Dict[str, Any]:
246
+ """
247
+ Get the status of a specific printer.
248
+
249
+ Args:
250
+ printer_id: ID of the printer to get status for
251
+
252
+ Returns:
253
+ Dictionary with printer status information
254
+ """
255
+ if printer_id not in self.connected_printers:
256
+ logger.error(f"Not connected to printer: {printer_id}")
257
+ return {"error": "Not connected to printer"}
258
+
259
+ client = self.connected_printers[printer_id]
260
+ return client.get_status()
261
+
262
+ def cancel_print(self, printer_id: str) -> bool:
263
+ """
264
+ Cancel a print job on a specific printer.
265
+
266
+ Args:
267
+ printer_id: ID of the printer to cancel print on
268
+
269
+ Returns:
270
+ True if cancellation successful, False otherwise
271
+ """
272
+ if printer_id not in self.connected_printers:
273
+ logger.error(f"Not connected to printer: {printer_id}")
274
+ return False
275
+
276
+ client = self.connected_printers[printer_id]
277
+ return client.cancel_print()
278
+
279
+
280
+ class PrinterClient:
281
+ """Base class for printer clients."""
282
+
283
+ def __init__(self, printer_info: Dict[str, Any], credentials: Optional[Dict[str, Any]] = None):
284
+ """
285
+ Initialize the printer client.
286
+
287
+ Args:
288
+ printer_info: Information about the printer
289
+ credentials: Optional credentials for authentication
290
+ """
291
+ self.printer_info = printer_info
292
+ self.credentials = credentials or {}
293
+ self.connected = False
294
+
295
+ def connect(self) -> bool:
296
+ """
297
+ Connect to the printer.
298
+
299
+ Returns:
300
+ True if connection successful, False otherwise
301
+ """
302
+ # Base implementation - should be overridden by subclasses
303
+ self.connected = True
304
+ return True
305
+
306
+ def disconnect(self) -> bool:
307
+ """
308
+ Disconnect from the printer.
309
+
310
+ Returns:
311
+ True if disconnection successful, False otherwise
312
+ """
313
+ # Base implementation - should be overridden by subclasses
314
+ self.connected = False
315
+ return True
316
+
317
+ def print_file(self, file_path: str, print_settings: Optional[Dict[str, Any]] = None) -> bool:
318
+ """
319
+ Send a file to the printer for printing.
320
+
321
+ Args:
322
+ file_path: Path to the STL file to print
323
+ print_settings: Optional print settings
324
+
325
+ Returns:
326
+ True if print job started successfully, False otherwise
327
+ """
328
+ # Base implementation - should be overridden by subclasses
329
+ if not self.connected:
330
+ logger.error("Not connected to printer")
331
+ return False
332
+
333
+ logger.info(f"Printing file: {file_path}")
334
+ return True
335
+
336
+ def get_status(self) -> Dict[str, Any]:
337
+ """
338
+ Get the status of the printer.
339
+
340
+ Returns:
341
+ Dictionary with printer status information
342
+ """
343
+ # Base implementation - should be overridden by subclasses
344
+ if not self.connected:
345
+ return {"status": "disconnected"}
346
+
347
+ return {"status": "connected"}
348
+
349
+ def cancel_print(self) -> bool:
350
+ """
351
+ Cancel the current print job.
352
+
353
+ Returns:
354
+ True if cancellation successful, False otherwise
355
+ """
356
+ # Base implementation - should be overridden by subclasses
357
+ if not self.connected:
358
+ logger.error("Not connected to printer")
359
+ return False
360
+
361
+ logger.info("Cancelling print job")
362
+ return True
363
+
364
+
365
+ class OctoPrintClient(PrinterClient):
366
+ """Client for OctoPrint printers."""
367
+
368
+ def connect(self) -> bool:
369
+ """Connect to an OctoPrint server."""
370
+ try:
371
+ # In a real implementation, you would use the OctoPrint API
372
+ # to connect to the printer
373
+
374
+ # Check if API key is provided
375
+ if "api_key" not in self.credentials:
376
+ logger.error("API key required for OctoPrint")
377
+ return False
378
+
379
+ # Simulate connection
380
+ logger.info(f"Connected to OctoPrint server: {self.printer_info['address']}")
381
+ self.connected = True
382
+ return True
383
+ except Exception as e:
384
+ logger.error(f"Error connecting to OctoPrint server: {str(e)}")
385
+ return False
386
+
387
+ def print_file(self, file_path: str, print_settings: Optional[Dict[str, Any]] = None) -> bool:
388
+ """Send a file to an OctoPrint server for printing."""
389
+ if not self.connected:
390
+ logger.error("Not connected to OctoPrint server")
391
+ return False
392
+
393
+ try:
394
+ # In a real implementation, you would use the OctoPrint API
395
+ # to upload the file and start printing
396
+
397
+ # Check if file exists
398
+ if not os.path.exists(file_path):
399
+ logger.error(f"File not found: {file_path}")
400
+ return False
401
+
402
+ # Simulate printing
403
+ logger.info(f"Printing file on OctoPrint server: {file_path}")
404
+ return True
405
+ except Exception as e:
406
+ logger.error(f"Error printing file on OctoPrint server: {str(e)}")
407
+ return False
408
+
409
+ def get_status(self) -> Dict[str, Any]:
410
+ """Get the status of an OctoPrint server."""
411
+ if not self.connected:
412
+ return {"status": "disconnected"}
413
+
414
+ try:
415
+ # In a real implementation, you would use the OctoPrint API
416
+ # to get the printer status
417
+
418
+ # Simulate status
419
+ return {
420
+ "status": "connected",
421
+ "printer": {
422
+ "state": "operational",
423
+ "temperature": {
424
+ "bed": {"actual": 60.0, "target": 60.0},
425
+ "tool0": {"actual": 210.0, "target": 210.0}
426
+ }
427
+ },
428
+ "job": {
429
+ "file": {"name": "example.gcode"},
430
+ "progress": {"completion": 0.0, "printTime": 0, "printTimeLeft": 0}
431
+ }
432
+ }
433
+ except Exception as e:
434
+ logger.error(f"Error getting status from OctoPrint server: {str(e)}")
435
+ return {"status": "error", "message": str(e)}
436
+
437
+
438
+ class PrusaClient(PrinterClient):
439
+ """Client for Prusa printers."""
440
+
441
+ def connect(self) -> bool:
442
+ """Connect to a Prusa printer."""
443
+ try:
444
+ # In a real implementation, you would use the Prusa API
445
+ # to connect to the printer
446
+
447
+ # Simulate connection
448
+ logger.info(f"Connected to Prusa printer: {self.printer_info['address']}")
449
+ self.connected = True
450
+ return True
451
+ except Exception as e:
452
+ logger.error(f"Error connecting to Prusa printer: {str(e)}")
453
+ return False
454
+
455
+
456
+ class UltimakerClient(PrinterClient):
457
+ """Client for Ultimaker printers."""
458
+
459
+ def connect(self) -> bool:
460
+ """Connect to an Ultimaker printer."""
461
+ try:
462
+ # In a real implementation, you would use the Ultimaker API
463
+ # to connect to the printer
464
+
465
+ # Simulate connection
466
+ logger.info(f"Connected to Ultimaker printer: {self.printer_info['address']}")
467
+ self.connected = True
468
+ return True
469
+ except Exception as e:
470
+ logger.error(f"Error connecting to Ultimaker printer: {str(e)}")
471
+ return False