@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,418 @@
1
+ import os
2
+ import subprocess
3
+ import uuid
4
+ import logging
5
+ from typing import Dict, Any, List, Tuple, Optional
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class OpenSCADWrapper:
10
+ """
11
+ Wrapper for OpenSCAD command-line interface.
12
+ Provides methods to generate SCAD code, STL files, and preview images.
13
+ """
14
+
15
+ def __init__(self, scad_dir: str, output_dir: str):
16
+ """
17
+ Initialize the OpenSCAD wrapper.
18
+
19
+ Args:
20
+ scad_dir: Directory to store SCAD files
21
+ output_dir: Directory to store output files (STL, PNG)
22
+ """
23
+ self.scad_dir = scad_dir
24
+ self.output_dir = output_dir
25
+ self.stl_dir = os.path.join(output_dir, "stl")
26
+ self.preview_dir = os.path.join(output_dir, "preview")
27
+
28
+ # Create directories if they don't exist
29
+ os.makedirs(self.scad_dir, exist_ok=True)
30
+ os.makedirs(self.stl_dir, exist_ok=True)
31
+ os.makedirs(self.preview_dir, exist_ok=True)
32
+
33
+ # Basic shape templates
34
+ self.shape_templates = {
35
+ "cube": self._cube_template,
36
+ "sphere": self._sphere_template,
37
+ "cylinder": self._cylinder_template,
38
+ "box": self._box_template,
39
+ "rounded_box": self._rounded_box_template,
40
+ }
41
+
42
+ def generate_scad_code(self, model_type: str, parameters: Dict[str, Any]) -> str:
43
+ """
44
+ Generate OpenSCAD code for a given model type and parameters.
45
+
46
+ Args:
47
+ model_type: Type of model to generate (cube, sphere, cylinder, etc.)
48
+ parameters: Dictionary of parameters for the model
49
+
50
+ Returns:
51
+ Path to the generated SCAD file
52
+ """
53
+ model_id = str(uuid.uuid4())
54
+ scad_file = os.path.join(self.scad_dir, f"{model_id}.scad")
55
+
56
+ # Get the template function for the model type
57
+ template_func = self.shape_templates.get(model_type)
58
+ if not template_func:
59
+ raise ValueError(f"Unsupported model type: {model_type}")
60
+
61
+ # Generate SCAD code using the template
62
+ scad_code = template_func(parameters)
63
+
64
+ # Write SCAD code to file
65
+ with open(scad_file, 'w') as f:
66
+ f.write(scad_code)
67
+
68
+ logger.info(f"Generated SCAD file: {scad_file}")
69
+ return scad_file
70
+
71
+ def generate_scad(self, scad_code: str, model_id: str) -> str:
72
+ """
73
+ Save OpenSCAD code to a file with a specific model ID.
74
+
75
+ Args:
76
+ scad_code: OpenSCAD code to save
77
+ model_id: ID to use for the file name
78
+
79
+ Returns:
80
+ Path to the saved SCAD file
81
+ """
82
+ scad_file = os.path.join(self.scad_dir, f"{model_id}.scad")
83
+
84
+ # Write SCAD code to file
85
+ with open(scad_file, 'w') as f:
86
+ f.write(scad_code)
87
+
88
+ logger.info(f"Generated SCAD file: {scad_file}")
89
+ return scad_file
90
+
91
+ def update_scad_code(self, model_id: str, parameters: Dict[str, Any]) -> str:
92
+ """
93
+ Update an existing SCAD file with new parameters.
94
+
95
+ Args:
96
+ model_id: ID of the model to update
97
+ parameters: New parameters for the model
98
+
99
+ Returns:
100
+ Path to the updated SCAD file
101
+ """
102
+ scad_file = os.path.join(self.scad_dir, f"{model_id}.scad")
103
+ if not os.path.exists(scad_file):
104
+ raise FileNotFoundError(f"SCAD file not found: {scad_file}")
105
+
106
+ # Read the existing SCAD file to determine its type
107
+ with open(scad_file, 'r') as f:
108
+ scad_code = f.read()
109
+
110
+ # Determine model type from the code (simplified approach)
111
+ model_type = None
112
+ for shape_type in self.shape_templates:
113
+ if shape_type in scad_code.lower():
114
+ model_type = shape_type
115
+ break
116
+
117
+ if not model_type:
118
+ raise ValueError("Could not determine model type from existing SCAD file")
119
+
120
+ # Generate new SCAD code
121
+ new_scad_code = self.shape_templates[model_type](parameters)
122
+
123
+ # Write updated SCAD code to file
124
+ with open(scad_file, 'w') as f:
125
+ f.write(new_scad_code)
126
+
127
+ logger.info(f"Updated SCAD file: {scad_file}")
128
+ return scad_file
129
+
130
+ def generate_stl(self, scad_file: str, parameters: Optional[Dict[str, Any]] = None) -> str:
131
+ """
132
+ Generate an STL file from a SCAD file.
133
+
134
+ Args:
135
+ scad_file: Path to the SCAD file
136
+ parameters: Optional parameters to override in the SCAD file
137
+
138
+ Returns:
139
+ Path to the generated STL file
140
+ """
141
+ model_id = os.path.basename(scad_file).split('.')[0]
142
+ stl_file = os.path.join(self.stl_dir, f"{model_id}.stl")
143
+
144
+ # Build command
145
+ cmd = ["openscad", "-o", stl_file]
146
+
147
+ # Add parameters if provided
148
+ if parameters:
149
+ for key, value in parameters.items():
150
+ cmd.extend(["-D", f"{key}={value}"])
151
+
152
+ # Add input file
153
+ cmd.append(scad_file)
154
+
155
+ # Run OpenSCAD
156
+ try:
157
+ result = subprocess.run(cmd, check=True, capture_output=True, text=True)
158
+ logger.info(f"Generated STL file: {stl_file}")
159
+ logger.debug(result.stdout)
160
+ return stl_file
161
+ except subprocess.CalledProcessError as e:
162
+ logger.error(f"Error generating STL file: {e.stderr}")
163
+ raise RuntimeError(f"Failed to generate STL file: {e.stderr}")
164
+
165
+ def generate_preview(self, scad_file: str, parameters: Optional[Dict[str, Any]] = None,
166
+ camera_position: str = "0,0,0,0,0,0,50",
167
+ image_size: str = "800,600") -> str:
168
+ """
169
+ Generate a preview image from a SCAD file.
170
+
171
+ Args:
172
+ scad_file: Path to the SCAD file
173
+ parameters: Optional parameters to override in the SCAD file
174
+ camera_position: Camera position in format "tx,ty,tz,rx,ry,rz,dist"
175
+ image_size: Image size in format "width,height"
176
+
177
+ Returns:
178
+ Path to the generated preview image
179
+ """
180
+ model_id = os.path.basename(scad_file).split('.')[0]
181
+ preview_file = os.path.join(self.preview_dir, f"{model_id}.png")
182
+
183
+ # Build command
184
+ cmd = ["openscad", "--camera", camera_position, "--imgsize", image_size, "-o", preview_file]
185
+
186
+ # Add parameters if provided
187
+ if parameters:
188
+ for key, value in parameters.items():
189
+ cmd.extend(["-D", f"{key}={value}"])
190
+
191
+ # Add input file
192
+ cmd.append(scad_file)
193
+
194
+ # Run OpenSCAD
195
+ try:
196
+ result = subprocess.run(cmd, check=True, capture_output=True, text=True)
197
+ logger.info(f"Generated preview image: {preview_file}")
198
+ logger.debug(result.stdout)
199
+ return preview_file
200
+ except subprocess.CalledProcessError as e:
201
+ logger.error(f"Error generating preview image: {e.stderr}")
202
+ # Since we know there might be issues with headless rendering, we'll create a placeholder
203
+ logger.warning("Using placeholder image due to rendering error")
204
+ return self._create_placeholder_image(preview_file)
205
+
206
+ def _create_placeholder_image(self, output_path: str) -> str:
207
+ """Create a simple placeholder image when rendering fails."""
208
+ try:
209
+ from PIL import Image, ImageDraw, ImageFont
210
+
211
+ # Create a blank image
212
+ img = Image.new('RGB', (800, 600), color=(240, 240, 240))
213
+ draw = ImageDraw.Draw(img)
214
+
215
+ # Add text
216
+ draw.text((400, 300), "Preview not available", fill=(0, 0, 0))
217
+
218
+ # Save the image
219
+ img.save(output_path)
220
+ return output_path
221
+ except Exception as e:
222
+ logger.error(f"Error creating placeholder image: {str(e)}")
223
+ # If all else fails, return the path anyway
224
+ return output_path
225
+
226
+ def generate_multi_angle_previews(self, scad_file: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
227
+ """
228
+ Generate preview images from multiple angles for a SCAD file.
229
+
230
+ Args:
231
+ scad_file: Path to the SCAD file
232
+ parameters: Optional parameters to override in the SCAD file
233
+
234
+ Returns:
235
+ Dictionary mapping view names to preview image paths
236
+ """
237
+ # Define camera positions for different views
238
+ camera_positions = {
239
+ "front": "0,0,0,0,0,0,50",
240
+ "top": "0,0,0,90,0,0,50",
241
+ "right": "0,0,0,0,90,0,50",
242
+ "perspective": "40,30,30,55,0,25,100"
243
+ }
244
+
245
+ # Generate preview for each view
246
+ previews = {}
247
+ for view, camera_position in camera_positions.items():
248
+ try:
249
+ model_id = os.path.basename(scad_file).split('.')[0]
250
+ preview_file = os.path.join(self.preview_dir, f"{model_id}_{view}.png")
251
+
252
+ # Build command
253
+ cmd = ["openscad", "--camera", camera_position, "--imgsize", "800,600", "-o", preview_file]
254
+
255
+ # Add parameters if provided
256
+ if parameters:
257
+ for key, value in parameters.items():
258
+ cmd.extend(["-D", f"{key}={value}"])
259
+
260
+ # Add input file
261
+ cmd.append(scad_file)
262
+
263
+ # Run OpenSCAD
264
+ result = subprocess.run(cmd, check=True, capture_output=True, text=True)
265
+ logger.info(f"Generated {view} preview: {preview_file}")
266
+ previews[view] = preview_file
267
+ except subprocess.CalledProcessError as e:
268
+ logger.error(f"Error generating {view} preview: {e.stderr}")
269
+ # Create a placeholder image for this view
270
+ preview_file = os.path.join(self.preview_dir, f"{model_id}_{view}.png")
271
+ previews[view] = self._create_placeholder_image(preview_file)
272
+
273
+ return previews
274
+
275
+ # Template functions for basic shapes
276
+
277
+ def _cube_template(self, params: Dict[str, Any]) -> str:
278
+ """Generate SCAD code for a cube."""
279
+ size_x = params.get('width', 10)
280
+ size_y = params.get('depth', 10)
281
+ size_z = params.get('height', 10)
282
+ center = params.get('center', 'false').lower() == 'true'
283
+
284
+ return f"""// Cube
285
+ // Parameters:
286
+ // width = {size_x}
287
+ // depth = {size_y}
288
+ // height = {size_z}
289
+ // center = {str(center).lower()}
290
+
291
+ width = {size_x};
292
+ depth = {size_y};
293
+ height = {size_z};
294
+ center = {str(center).lower()};
295
+
296
+ cube([width, depth, height], center=center);
297
+ """
298
+
299
+ def _sphere_template(self, params: Dict[str, Any]) -> str:
300
+ """Generate SCAD code for a sphere."""
301
+ radius = params.get('radius', 10)
302
+ segments = params.get('segments', 32)
303
+
304
+ return f"""// Sphere
305
+ // Parameters:
306
+ // radius = {radius}
307
+ // segments = {segments}
308
+
309
+ radius = {radius};
310
+ $fn = {segments};
311
+
312
+ sphere(r=radius);
313
+ """
314
+
315
+ def _cylinder_template(self, params: Dict[str, Any]) -> str:
316
+ """Generate SCAD code for a cylinder."""
317
+ radius = params.get('radius', 10)
318
+ height = params.get('height', 20)
319
+ center = params.get('center', 'false').lower() == 'true'
320
+ segments = params.get('segments', 32)
321
+
322
+ return f"""// Cylinder
323
+ // Parameters:
324
+ // radius = {radius}
325
+ // height = {height}
326
+ // center = {str(center).lower()}
327
+ // segments = {segments}
328
+
329
+ radius = {radius};
330
+ height = {height};
331
+ center = {str(center).lower()};
332
+ $fn = {segments};
333
+
334
+ cylinder(h=height, r=radius, center=center);
335
+ """
336
+
337
+ def _box_template(self, params: Dict[str, Any]) -> str:
338
+ """Generate SCAD code for a hollow box."""
339
+ width = params.get('width', 30)
340
+ depth = params.get('depth', 20)
341
+ height = params.get('height', 15)
342
+ thickness = params.get('thickness', 2)
343
+
344
+ return f"""// Hollow Box
345
+ // Parameters:
346
+ // width = {width}
347
+ // depth = {depth}
348
+ // height = {height}
349
+ // thickness = {thickness}
350
+
351
+ width = {width};
352
+ depth = {depth};
353
+ height = {height};
354
+ thickness = {thickness};
355
+
356
+ module box(width, depth, height, thickness) {{
357
+ difference() {{
358
+ cube([width, depth, height]);
359
+ translate([thickness, thickness, thickness])
360
+ cube([width - 2*thickness, depth - 2*thickness, height - thickness]);
361
+ }}
362
+ }}
363
+
364
+ box(width, depth, height, thickness);
365
+ """
366
+
367
+ def _rounded_box_template(self, params: Dict[str, Any]) -> str:
368
+ """Generate SCAD code for a rounded box."""
369
+ width = params.get('width', 30)
370
+ depth = params.get('depth', 20)
371
+ height = params.get('height', 15)
372
+ radius = params.get('radius', 3)
373
+ segments = params.get('segments', 32)
374
+
375
+ return f"""// Rounded Box
376
+ // Parameters:
377
+ // width = {width}
378
+ // depth = {depth}
379
+ // height = {height}
380
+ // radius = {radius}
381
+ // segments = {segments}
382
+
383
+ width = {width};
384
+ depth = {depth};
385
+ height = {height};
386
+ radius = {radius};
387
+ $fn = {segments};
388
+
389
+ module rounded_box(width, depth, height, radius) {{
390
+ hull() {{
391
+ translate([radius, radius, radius])
392
+ sphere(r=radius);
393
+
394
+ translate([width-radius, radius, radius])
395
+ sphere(r=radius);
396
+
397
+ translate([radius, depth-radius, radius])
398
+ sphere(r=radius);
399
+
400
+ translate([width-radius, depth-radius, radius])
401
+ sphere(r=radius);
402
+
403
+ translate([radius, radius, height-radius])
404
+ sphere(r=radius);
405
+
406
+ translate([width-radius, radius, height-radius])
407
+ sphere(r=radius);
408
+
409
+ translate([radius, depth-radius, height-radius])
410
+ sphere(r=radius);
411
+
412
+ translate([width-radius, depth-radius, height-radius])
413
+ sphere(r=radius);
414
+ }}
415
+ }}
416
+
417
+ rounded_box(width, depth, height, radius);
418
+ """
@@ -0,0 +1 @@
1
+ # Printer discovery package