@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,321 @@
1
+ import os
2
+ import logging
3
+ import uuid
4
+ from typing import Dict, Any, List, Tuple, Optional
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class CodeGenerator:
9
+ """
10
+ Generates OpenSCAD code from natural language descriptions and parameters.
11
+ Implements translation of requirements to OpenSCAD primitives and modules.
12
+ """
13
+
14
+ def __init__(self, scad_templates_dir: str, output_dir: str, ai_service=None):
15
+ """
16
+ Initialize the code generator.
17
+
18
+ Args:
19
+ scad_templates_dir: Directory containing SCAD template files
20
+ output_dir: Directory to store generated SCAD files
21
+ ai_service: Optional AI service for enhanced code generation
22
+ """
23
+ self.scad_templates_dir = scad_templates_dir
24
+ self.output_dir = output_dir
25
+ self.ai_service = ai_service
26
+
27
+ # Create output directory if it doesn't exist
28
+ os.makedirs(output_dir, exist_ok=True)
29
+
30
+ # Map of shape types to their corresponding module names
31
+ self.shape_module_map = {
32
+ 'cube': 'parametric_cube',
33
+ 'sphere': 'parametric_sphere',
34
+ 'cylinder': 'parametric_cylinder',
35
+ 'box': 'hollow_box',
36
+ 'rounded_box': 'rounded_box',
37
+ 'container': 'rounded_container',
38
+ 'tube': 'tube',
39
+ 'cone': 'cone',
40
+ 'wedge': 'wedge',
41
+ 'rounded_cylinder': 'rounded_cylinder',
42
+ 'torus': 'torus',
43
+ 'hexagonal_prism': 'hexagonal_prism',
44
+ 'text': 'text_3d',
45
+ 'prism': 'triangular_prism',
46
+ 'custom': 'custom_shape'
47
+ }
48
+
49
+ # Parameter mapping from natural language to OpenSCAD parameters
50
+ self.parameter_map = {
51
+ 'width': 'width',
52
+ 'depth': 'depth',
53
+ 'height': 'height',
54
+ 'radius': 'radius',
55
+ 'thickness': 'thickness',
56
+ 'segments': 'segments',
57
+ 'center': 'center',
58
+ 'inner_radius': 'inner_radius',
59
+ 'outer_radius': 'outer_radius',
60
+ 'corner_radius': 'corner_radius',
61
+ 'text': 'text',
62
+ 'size': 'size',
63
+ 'font': 'font',
64
+ 'base_radius': 'base_radius',
65
+ 'major_radius': 'major_radius',
66
+ 'minor_radius': 'minor_radius',
67
+ 'angle': 'angle',
68
+ 'scale': 'scale',
69
+ 'resolution': 'resolution'
70
+ }
71
+
72
+ def generate_code(self, model_type: str, parameters: Dict[str, Any], description: Optional[str] = None) -> str:
73
+ """
74
+ Generate OpenSCAD code for a given model type and parameters.
75
+
76
+ Args:
77
+ model_type: Type of model to generate
78
+ parameters: Dictionary of parameters for the model
79
+ description: Optional natural language description for AI-driven generation
80
+
81
+ Returns:
82
+ Path to the generated SCAD file
83
+ """
84
+ # Generate a unique ID for the model
85
+ model_id = str(uuid.uuid4())
86
+ scad_file = os.path.join(self.output_dir, f"{model_id}.scad")
87
+
88
+ # Check if we should use AI-driven generation for complex models
89
+ if model_type == 'custom' and description and self.ai_service:
90
+ scad_code = self._generate_ai_driven_code(description, parameters)
91
+ else:
92
+ # Get the module name for the model type
93
+ module_name = self.shape_module_map.get(model_type)
94
+ if not module_name:
95
+ raise ValueError(f"Unsupported model type: {model_type}")
96
+
97
+ # Map parameters to OpenSCAD parameter names
98
+ scad_params = self._map_parameters(parameters)
99
+
100
+ # Generate the OpenSCAD code
101
+ scad_code = self._generate_scad_code(module_name, scad_params)
102
+
103
+ # Write the code to a file
104
+ with open(scad_file, 'w') as f:
105
+ f.write(scad_code)
106
+
107
+ logger.info(f"Generated OpenSCAD code: {scad_file}")
108
+ return scad_file
109
+
110
+ def update_code(self, scad_file: str, parameters: Dict[str, Any]) -> str:
111
+ """
112
+ Update an existing SCAD file with new parameters.
113
+
114
+ Args:
115
+ scad_file: Path to the SCAD file to update
116
+ parameters: New parameters to apply
117
+
118
+ Returns:
119
+ Path to the updated SCAD file
120
+ """
121
+ if not os.path.exists(scad_file):
122
+ raise FileNotFoundError(f"SCAD file not found: {scad_file}")
123
+
124
+ # Read the existing SCAD file
125
+ with open(scad_file, 'r') as f:
126
+ scad_code = f.read()
127
+
128
+ # Determine the module name from the code
129
+ module_name = None
130
+ for shape_type, module in self.shape_module_map.items():
131
+ if module in scad_code:
132
+ module_name = module
133
+ break
134
+
135
+ if not module_name:
136
+ raise ValueError("Could not determine module name from existing SCAD file")
137
+
138
+ # Map parameters to OpenSCAD parameter names
139
+ scad_params = self._map_parameters(parameters)
140
+
141
+ # Generate the updated OpenSCAD code
142
+ updated_code = self._generate_scad_code(module_name, scad_params)
143
+
144
+ # Write the updated code to the file
145
+ with open(scad_file, 'w') as f:
146
+ f.write(updated_code)
147
+
148
+ logger.info(f"Updated OpenSCAD code: {scad_file}")
149
+ return scad_file
150
+
151
+ def combine_models(self, operations: List[Dict[str, Any]]) -> str:
152
+ """
153
+ Combine multiple models using CSG operations.
154
+
155
+ Args:
156
+ operations: List of operations, each containing:
157
+ - model_type: Type of model
158
+ - parameters: Parameters for the model
159
+ - operation: CSG operation (union, difference, intersection)
160
+ - transform: Optional transformation to apply
161
+
162
+ Returns:
163
+ Path to the generated SCAD file
164
+ """
165
+ # Generate a unique ID for the combined model
166
+ model_id = str(uuid.uuid4())
167
+ scad_file = os.path.join(self.output_dir, f"{model_id}.scad")
168
+
169
+ # Include the basic shapes library
170
+ scad_code = f"""// Combined model
171
+ include <{os.path.join(self.scad_templates_dir, "basic_shapes.scad")}>;
172
+
173
+ """
174
+
175
+ # Process each operation
176
+ current_op = None
177
+ for i, op in enumerate(operations):
178
+ model_type = op.get('model_type')
179
+ parameters = op.get('parameters', {})
180
+ operation = op.get('operation')
181
+ transform = op.get('transform')
182
+
183
+ # Get the module name for the model type
184
+ if model_type is None:
185
+ raise ValueError("Model type cannot be None")
186
+ module_name = self.shape_module_map.get(str(model_type))
187
+ if not module_name:
188
+ raise ValueError(f"Unsupported model type: {model_type}")
189
+
190
+ # Map parameters to OpenSCAD parameter names
191
+ scad_params = self._map_parameters(parameters)
192
+
193
+ # Format parameters for the module call
194
+ params_str = ", ".join([f"{k}={v}" for k, v in scad_params.items()])
195
+
196
+ # Start or continue the CSG operation chain
197
+ if i == 0:
198
+ # First operation doesn't need an operator
199
+ if operation:
200
+ current_op = operation
201
+ scad_code += f"{operation}() {{\n"
202
+
203
+ # Add the module call with optional transformation
204
+ if transform:
205
+ scad_code += f" {transform} {module_name}({params_str});\n"
206
+ else:
207
+ scad_code += f" {module_name}({params_str});\n"
208
+ else:
209
+ # Check if we need to close the previous operation and start a new one
210
+ if operation and operation != current_op:
211
+ if current_op:
212
+ scad_code += "}\n\n"
213
+ current_op = operation
214
+ scad_code += f"{operation}() {{\n"
215
+
216
+ # Add the module call with optional transformation
217
+ if transform:
218
+ scad_code += f" {transform} {module_name}({params_str});\n"
219
+ else:
220
+ scad_code += f" {module_name}({params_str});\n"
221
+
222
+ # Close the final operation if needed
223
+ if current_op:
224
+ scad_code += "}\n"
225
+
226
+ # Write the code to a file
227
+ with open(scad_file, 'w') as f:
228
+ f.write(scad_code)
229
+
230
+ logger.info(f"Generated combined OpenSCAD code: {scad_file}")
231
+ return scad_file
232
+
233
+ def _map_parameters(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
234
+ """Map natural language parameters to OpenSCAD parameters."""
235
+ scad_params = {}
236
+
237
+ for param, value in parameters.items():
238
+ # Map the parameter name if it exists in the mapping
239
+ scad_param = self.parameter_map.get(param, param)
240
+
241
+ # Format the value appropriately for OpenSCAD
242
+ if isinstance(value, bool):
243
+ scad_params[scad_param] = str(value).lower()
244
+ elif isinstance(value, str):
245
+ if value.lower() == 'true' or value.lower() == 'false':
246
+ scad_params[scad_param] = value.lower()
247
+ else:
248
+ # For text parameters, add quotes
249
+ scad_params[scad_param] = f'"{value}"'
250
+ else:
251
+ scad_params[scad_param] = value
252
+
253
+ return scad_params
254
+
255
+ def _generate_scad_code(self, module_name: str, parameters: Dict[str, Any]) -> str:
256
+ """Generate OpenSCAD code for a module with parameters."""
257
+ # Include the basic shapes library
258
+ scad_code = f"""// Generated OpenSCAD code
259
+ include <{os.path.join(self.scad_templates_dir, "basic_shapes.scad")}>;
260
+
261
+ // Parameters
262
+ """
263
+
264
+ # Add parameter declarations
265
+ for param, value in parameters.items():
266
+ scad_code += f"{param} = {value};\n"
267
+
268
+ # Add the module call
269
+ scad_code += f"\n// Model\n{module_name}("
270
+
271
+ # Add parameters to the module call
272
+ param_list = [f"{param}={param}" for param in parameters.keys()]
273
+ scad_code += ", ".join(param_list)
274
+
275
+ scad_code += ");\n"
276
+
277
+ return scad_code
278
+
279
+ def _generate_ai_driven_code(self, description: str, parameters: Dict[str, Any]) -> str:
280
+ """
281
+ Generate OpenSCAD code using AI-driven techniques based on natural language description.
282
+
283
+ Args:
284
+ description: Natural language description of the model
285
+ parameters: Dictionary of parameters for the model
286
+
287
+ Returns:
288
+ Generated OpenSCAD code
289
+ """
290
+ if not self.ai_service:
291
+ logger.warning("AI service not available, falling back to basic shape generation")
292
+ # Fall back to a basic cube if AI service is not available
293
+ return self._generate_scad_code('parametric_cube', {'width': 10, 'height': 10, 'depth': 10})
294
+
295
+ try:
296
+ # Use the AI service to generate OpenSCAD code
297
+ logger.info(f"Generating OpenSCAD code from description: {description}")
298
+
299
+ # Prepare context for the AI service
300
+ context = {
301
+ "description": description,
302
+ "parameters": parameters,
303
+ "templates_dir": self.scad_templates_dir
304
+ }
305
+
306
+ # Call the AI service to generate code
307
+ scad_code = self.ai_service.generate_openscad_code(context)
308
+
309
+ # Ensure the code includes the basic shapes library
310
+ if "include <" not in scad_code:
311
+ scad_code = f"""// AI-generated OpenSCAD code
312
+ include <{os.path.join(self.scad_templates_dir, "basic_shapes.scad")}>;
313
+
314
+ {scad_code}
315
+ """
316
+
317
+ return scad_code
318
+ except Exception as e:
319
+ logger.error(f"Error generating AI-driven code: {e}")
320
+ # Fall back to a basic shape if there's an error
321
+ return self._generate_scad_code('parametric_cube', {'width': 10, 'height': 10, 'depth': 10})
@@ -0,0 +1,209 @@
1
+ """
2
+ CUDA Multi-View Stereo wrapper for 3D reconstruction from multiple images.
3
+ """
4
+
5
+ import os
6
+ import subprocess
7
+ import logging
8
+ import json
9
+ from typing import Dict, Any, List, Optional
10
+ from pathlib import Path
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class CUDAMultiViewStereo:
15
+ """
16
+ Wrapper for CUDA Multi-View Stereo for 3D reconstruction from multiple images.
17
+ """
18
+
19
+ def __init__(self, cuda_mvs_path: str, output_dir: str = "output/models"):
20
+ """
21
+ Initialize the CUDA MVS wrapper.
22
+
23
+ Args:
24
+ cuda_mvs_path: Path to CUDA MVS installation
25
+ output_dir: Directory to store output files
26
+ """
27
+ self.cuda_mvs_path = cuda_mvs_path
28
+ self.output_dir = output_dir
29
+
30
+ # Create output directory if it doesn't exist
31
+ os.makedirs(output_dir, exist_ok=True)
32
+
33
+ # Validate installation
34
+ self._validate_installation()
35
+
36
+ def _validate_installation(self) -> None:
37
+ """
38
+ Validate CUDA MVS installation.
39
+
40
+ Raises:
41
+ FileNotFoundError: If CUDA MVS installation is not found
42
+ """
43
+ if not os.path.exists(self.cuda_mvs_path):
44
+ raise FileNotFoundError(f"CUDA MVS not found at {self.cuda_mvs_path}")
45
+
46
+ # Check for required executables
47
+ required_files = ["app_patch_match_mvs"]
48
+ for file in required_files:
49
+ exec_path = os.path.join(self.cuda_mvs_path, "build", file)
50
+ if not os.path.exists(exec_path):
51
+ raise FileNotFoundError(f"Required executable {file} not found at {exec_path}")
52
+
53
+ def generate_model_from_images(self, image_paths: List[str],
54
+ camera_params: Optional[Dict[str, Any]] = None,
55
+ output_name: str = "model") -> Dict[str, Any]:
56
+ """
57
+ Generate a 3D model from multiple images using CUDA MVS.
58
+
59
+ Args:
60
+ image_paths: List of paths to input images
61
+ camera_params: Optional camera parameters
62
+ output_name: Name for the output files
63
+
64
+ Returns:
65
+ Dictionary containing paths to generated model files
66
+ """
67
+ try:
68
+ # Create a unique directory for this reconstruction
69
+ model_dir = os.path.join(self.output_dir, output_name)
70
+ os.makedirs(model_dir, exist_ok=True)
71
+
72
+ # Create a camera parameters file if provided
73
+ params_file = None
74
+ if camera_params:
75
+ params_file = os.path.join(model_dir, "camera_params.json")
76
+ with open(params_file, 'w') as f:
77
+ json.dump(camera_params, f, indent=2)
78
+
79
+ # Generate camera parameters if not provided
80
+ if not params_file:
81
+ params_file = self._generate_camera_params(image_paths, model_dir)
82
+
83
+ # Generate point cloud
84
+ point_cloud_file = os.path.join(model_dir, f"{output_name}.ply")
85
+
86
+ # Run CUDA MVS
87
+ cmd = [
88
+ os.path.join(self.cuda_mvs_path, "build", "app_patch_match_mvs"),
89
+ "--image_dir", os.path.dirname(image_paths[0]),
90
+ "--camera_params", params_file,
91
+ "--output_file", point_cloud_file
92
+ ]
93
+
94
+ logger.info(f"Running CUDA MVS with command: {' '.join(cmd)}")
95
+
96
+ process = subprocess.Popen(
97
+ cmd,
98
+ stdout=subprocess.PIPE,
99
+ stderr=subprocess.PIPE,
100
+ text=True
101
+ )
102
+
103
+ # Wait for process to complete
104
+ stdout, stderr = process.communicate()
105
+
106
+ if process.returncode != 0:
107
+ logger.error(f"Error running CUDA MVS: {stderr}")
108
+ raise RuntimeError(f"CUDA MVS failed with exit code {process.returncode}")
109
+
110
+ # Check if output file was created
111
+ if not os.path.exists(point_cloud_file):
112
+ raise FileNotFoundError(f"Output point cloud file not found at {point_cloud_file}")
113
+
114
+ return {
115
+ "model_id": output_name,
116
+ "output_dir": model_dir,
117
+ "point_cloud_file": point_cloud_file,
118
+ "camera_params_file": params_file,
119
+ "input_images": image_paths
120
+ }
121
+
122
+ except Exception as e:
123
+ logger.error(f"Error generating 3D model with CUDA MVS: {str(e)}")
124
+ raise
125
+
126
+ def _generate_camera_params(self, image_paths: List[str], model_dir: str) -> str:
127
+ """
128
+ Generate camera parameters from images.
129
+
130
+ Args:
131
+ image_paths: List of paths to input images
132
+ model_dir: Directory to save parameter file
133
+
134
+ Returns:
135
+ Path to camera parameters file
136
+ """
137
+ # This is a simplified version for demonstration
138
+ # In a real implementation, this would use SfM or camera estimation
139
+
140
+ params = []
141
+ for i, img_path in enumerate(image_paths):
142
+ # Extract image dimensions
143
+ from PIL import Image
144
+ with Image.open(img_path) as img:
145
+ width, height = img.size
146
+
147
+ # Generate simple camera parameters
148
+ # In reality, these would be estimated from the images
149
+ # or provided by the user
150
+ params.append({
151
+ "image_id": i,
152
+ "image_name": os.path.basename(img_path),
153
+ "width": width,
154
+ "height": height,
155
+ "camera": {
156
+ "model": "PINHOLE",
157
+ "focal_length": min(width, height),
158
+ "principal_point": [width / 2, height / 2],
159
+ "rotation": [1, 0, 0, 0, 1, 0, 0, 0, 1],
160
+ "translation": [0, 0, 0]
161
+ }
162
+ })
163
+
164
+ # Write parameters to file
165
+ params_file = os.path.join(model_dir, "camera_params.json")
166
+ with open(params_file, 'w') as f:
167
+ json.dump(params, f, indent=2)
168
+
169
+ return params_file
170
+
171
+ def convert_ply_to_obj(self, ply_file: str, output_dir: Optional[str] = None) -> str:
172
+ """
173
+ Convert PLY point cloud to OBJ mesh.
174
+
175
+ Args:
176
+ ply_file: Path to input PLY file
177
+ output_dir: Directory to save output OBJ file
178
+
179
+ Returns:
180
+ Path to output OBJ file
181
+ """
182
+ # In a real implementation, this would use a mesh reconstruction library
183
+ # such as Open3D or PyMeshLab to convert the point cloud to a mesh
184
+
185
+ if not output_dir:
186
+ output_dir = os.path.dirname(ply_file)
187
+
188
+ # Generate output file path
189
+ obj_file = os.path.join(output_dir, f"{Path(ply_file).stem}.obj")
190
+
191
+ logger.info(f"Converting PLY to OBJ: {ply_file} -> {obj_file}")
192
+
193
+ # This is a placeholder for the actual conversion
194
+ # In a real implementation, you would use a library like Open3D:
195
+ # import open3d as o3d
196
+ # pcd = o3d.io.read_point_cloud(ply_file)
197
+ # mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd)[0]
198
+ # o3d.io.write_triangle_mesh(obj_file, mesh)
199
+
200
+ # For now, we'll just create a dummy OBJ file
201
+ with open(obj_file, 'w') as f:
202
+ f.write(f"# Converted from {os.path.basename(ply_file)}\n")
203
+ f.write("# This is a placeholder OBJ file\n")
204
+ f.write("v 0 0 0\n")
205
+ f.write("v 1 0 0\n")
206
+ f.write("v 0 1 0\n")
207
+ f.write("f 1 2 3\n")
208
+
209
+ return obj_file
@@ -0,0 +1,144 @@
1
+ // Basic Shapes Library for OpenSCAD MCP Server
2
+ // Contains reusable modules for common geometric patterns
3
+
4
+ // Basic cube with optional center parameter
5
+ module parametric_cube(width=10, depth=10, height=10, center=false) {
6
+ cube([width, depth, height], center=center);
7
+ }
8
+
9
+ // Basic sphere with customizable segments
10
+ module parametric_sphere(radius=10, segments=32) {
11
+ $fn = segments;
12
+ sphere(r=radius);
13
+ }
14
+
15
+ // Basic cylinder with customizable segments
16
+ module parametric_cylinder(radius=10, height=20, center=false, segments=32) {
17
+ $fn = segments;
18
+ cylinder(h=height, r=radius, center=center);
19
+ }
20
+
21
+ // Hollow box with customizable wall thickness
22
+ module hollow_box(width=30, depth=20, height=15, thickness=2) {
23
+ difference() {
24
+ cube([width, depth, height]);
25
+ translate([thickness, thickness, thickness])
26
+ cube([width - 2*thickness, depth - 2*thickness, height - thickness]);
27
+ }
28
+ }
29
+
30
+ // Rounded box with customizable corner radius
31
+ module rounded_box(width=30, depth=20, height=15, radius=3, segments=32) {
32
+ $fn = segments;
33
+ hull() {
34
+ translate([radius, radius, radius])
35
+ sphere(r=radius);
36
+
37
+ translate([width-radius, radius, radius])
38
+ sphere(r=radius);
39
+
40
+ translate([radius, depth-radius, radius])
41
+ sphere(r=radius);
42
+
43
+ translate([width-radius, depth-radius, radius])
44
+ sphere(r=radius);
45
+
46
+ translate([radius, radius, height-radius])
47
+ sphere(r=radius);
48
+
49
+ translate([width-radius, radius, height-radius])
50
+ sphere(r=radius);
51
+
52
+ translate([radius, depth-radius, height-radius])
53
+ sphere(r=radius);
54
+
55
+ translate([width-radius, depth-radius, height-radius])
56
+ sphere(r=radius);
57
+ }
58
+ }
59
+
60
+ // Rounded hollow box (container with rounded corners)
61
+ module rounded_container(width=30, depth=20, height=15, radius=3, thickness=2, segments=32) {
62
+ $fn = segments;
63
+ difference() {
64
+ rounded_box(width, depth, height, radius, segments);
65
+ translate([thickness, thickness, thickness])
66
+ rounded_box(
67
+ width - 2*thickness,
68
+ depth - 2*thickness,
69
+ height - thickness + 0.01, // Slight overlap to ensure clean difference
70
+ radius - thickness > 0 ? radius - thickness : 0.1,
71
+ segments
72
+ );
73
+ }
74
+ }
75
+
76
+ // Tube (hollow cylinder)
77
+ module tube(outer_radius=10, inner_radius=8, height=20, center=false, segments=32) {
78
+ $fn = segments;
79
+ difference() {
80
+ cylinder(h=height, r=outer_radius, center=center);
81
+ cylinder(h=height+0.01, r=inner_radius, center=center);
82
+ }
83
+ }
84
+
85
+ // Cone
86
+ module cone(bottom_radius=10, top_radius=0, height=20, center=false, segments=32) {
87
+ $fn = segments;
88
+ cylinder(h=height, r1=bottom_radius, r2=top_radius, center=center);
89
+ }
90
+
91
+ // Wedge (triangular prism)
92
+ module wedge(width=20, depth=20, height=10) {
93
+ polyhedron(
94
+ points=[
95
+ [0,0,0], [width,0,0], [width,depth,0], [0,depth,0],
96
+ [0,0,height], [width,0,0], [0,depth,0]
97
+ ],
98
+ faces=[
99
+ [0,1,2,3], // bottom
100
+ [4,5,1,0], // front
101
+ [4,0,3,6], // left
102
+ [6,3,2,5], // back
103
+ [4,6,5], // top
104
+ ]
105
+ );
106
+ }
107
+
108
+ // Rounded cylinder (cylinder with rounded top and bottom)
109
+ module rounded_cylinder(radius=10, height=20, corner_radius=2, center=false, segments=32) {
110
+ $fn = segments;
111
+ hull() {
112
+ translate([0, 0, corner_radius])
113
+ cylinder(h=height - 2*corner_radius, r=radius, center=center);
114
+
115
+ translate([0, 0, corner_radius])
116
+ rotate_extrude()
117
+ translate([radius - corner_radius, 0, 0])
118
+ circle(r=corner_radius);
119
+
120
+ translate([0, 0, height - corner_radius])
121
+ rotate_extrude()
122
+ translate([radius - corner_radius, 0, 0])
123
+ circle(r=corner_radius);
124
+ }
125
+ }
126
+
127
+ // Torus (donut shape)
128
+ module torus(outer_radius=20, inner_radius=5, segments=32) {
129
+ $fn = segments;
130
+ rotate_extrude()
131
+ translate([outer_radius - inner_radius, 0, 0])
132
+ circle(r=inner_radius);
133
+ }
134
+
135
+ // Hexagonal prism
136
+ module hexagonal_prism(radius=10, height=20, center=false) {
137
+ cylinder(h=height, r=radius, $fn=6, center=center);
138
+ }
139
+
140
+ // Text with customizable parameters
141
+ module text_3d(text="OpenSCAD", size=10, height=3, font="Liberation Sans", halign="center", valign="center") {
142
+ linear_extrude(height=height)
143
+ text(text=text, size=size, font=font, halign=halign, valign=valign);
144
+ }