@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,639 @@
1
+ import os
2
+ import base64
3
+ import logging
4
+ from typing import Dict, Any, List, Optional
5
+ from fastapi import APIRouter, HTTPException, Request
6
+ from fastapi.responses import HTMLResponse
7
+ from fastapi.templating import Jinja2Templates
8
+ from fastapi.staticfiles import StaticFiles
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class WebInterface:
13
+ """
14
+ Web interface for displaying model previews and managing 3D models.
15
+ """
16
+
17
+ def __init__(self, app, static_dir: str, templates_dir: str, output_dir: str):
18
+ """
19
+ Initialize the web interface.
20
+
21
+ Args:
22
+ app: FastAPI application
23
+ static_dir: Directory for static files
24
+ templates_dir: Directory for templates
25
+ output_dir: Directory containing output files (STL, PNG)
26
+ """
27
+ self.app = app
28
+ self.static_dir = static_dir
29
+ self.templates_dir = templates_dir
30
+ self.output_dir = output_dir
31
+ self.preview_dir = os.path.join(output_dir, "preview")
32
+ self.stl_dir = os.path.join(output_dir, "stl")
33
+
34
+ # Create directories if they don't exist
35
+ os.makedirs(self.static_dir, exist_ok=True)
36
+ os.makedirs(self.templates_dir, exist_ok=True)
37
+
38
+ # Create router
39
+ self.router = APIRouter(prefix="/ui", tags=["UI"])
40
+
41
+ # Set up static files
42
+ self.app.mount("/static", StaticFiles(directory=static_dir), name="static")
43
+
44
+ # Set up templates
45
+ self.templates = Jinja2Templates(directory=templates_dir)
46
+
47
+ # Register routes
48
+ self._register_routes()
49
+
50
+ # Create template files
51
+ self._create_template_files()
52
+
53
+ # Create static files
54
+ self._create_static_files()
55
+
56
+ def _register_routes(self):
57
+ """Register routes for the web interface."""
58
+ # Home page
59
+ @self.router.get("/", response_class=HTMLResponse)
60
+ async def home(request: Request):
61
+ return self.templates.TemplateResponse("index.html", {"request": request})
62
+
63
+ # Model preview page
64
+ @self.router.get("/preview/{model_id}", response_class=HTMLResponse)
65
+ async def preview(request: Request, model_id: str):
66
+ # Check if preview exists
67
+ preview_file = os.path.join(self.preview_dir, f"{model_id}.png")
68
+ if not os.path.exists(preview_file):
69
+ raise HTTPException(status_code=404, detail="Preview not found")
70
+
71
+ # Get multi-angle previews if they exist
72
+ angles = ["front", "top", "right", "perspective"]
73
+ previews = {}
74
+
75
+ for angle in angles:
76
+ angle_file = os.path.join(self.preview_dir, f"{model_id}_{angle}.png")
77
+ if os.path.exists(angle_file):
78
+ previews[angle] = f"/api/preview/{model_id}_{angle}"
79
+
80
+ # If no multi-angle previews, use the main preview
81
+ if not previews:
82
+ previews["main"] = f"/api/preview/{model_id}"
83
+
84
+ # Check if STL exists
85
+ stl_file = os.path.join(self.stl_dir, f"{model_id}.stl")
86
+ stl_url = f"/api/stl/{model_id}" if os.path.exists(stl_file) else None
87
+
88
+ return self.templates.TemplateResponse(
89
+ "preview.html",
90
+ {
91
+ "request": request,
92
+ "model_id": model_id,
93
+ "previews": previews,
94
+ "stl_url": stl_url
95
+ }
96
+ )
97
+
98
+ # List all models
99
+ @self.router.get("/models", response_class=HTMLResponse)
100
+ async def list_models(request: Request):
101
+ # Get all STL files
102
+ stl_files = []
103
+ if os.path.exists(self.stl_dir):
104
+ stl_files = [f for f in os.listdir(self.stl_dir) if f.endswith(".stl")]
105
+
106
+ # Extract model IDs
107
+ model_ids = [os.path.splitext(f)[0] for f in stl_files]
108
+
109
+ # Get preview URLs
110
+ models = []
111
+ for model_id in model_ids:
112
+ preview_file = os.path.join(self.preview_dir, f"{model_id}.png")
113
+ preview_url = f"/api/preview/{model_id}" if os.path.exists(preview_file) else None
114
+ stl_url = f"/api/stl/{model_id}"
115
+
116
+ models.append({
117
+ "id": model_id,
118
+ "preview_url": preview_url,
119
+ "stl_url": stl_url
120
+ })
121
+
122
+ return self.templates.TemplateResponse(
123
+ "models.html",
124
+ {"request": request, "models": models}
125
+ )
126
+
127
+ # API endpoints for serving files
128
+
129
+ # Serve preview image
130
+ @self.app.get("/api/preview/{preview_id}")
131
+ async def get_preview(preview_id: str):
132
+ preview_file = os.path.join(self.preview_dir, f"{preview_id}.png")
133
+ if not os.path.exists(preview_file):
134
+ raise HTTPException(status_code=404, detail="Preview not found")
135
+
136
+ # Return the file
137
+ with open(preview_file, "rb") as f:
138
+ content = f.read()
139
+
140
+ return {
141
+ "content": base64.b64encode(content).decode("utf-8"),
142
+ "content_type": "image/png"
143
+ }
144
+
145
+ # Serve STL file
146
+ @self.app.get("/api/stl/{model_id}")
147
+ async def get_stl(model_id: str):
148
+ stl_file = os.path.join(self.stl_dir, f"{model_id}.stl")
149
+ if not os.path.exists(stl_file):
150
+ raise HTTPException(status_code=404, detail="STL file not found")
151
+
152
+ # Return the file
153
+ with open(stl_file, "rb") as f:
154
+ content = f.read()
155
+
156
+ return {
157
+ "content": base64.b64encode(content).decode("utf-8"),
158
+ "content_type": "application/octet-stream",
159
+ "filename": f"{model_id}.stl"
160
+ }
161
+
162
+ # Register the router with the app
163
+ self.app.include_router(self.router)
164
+
165
+ def _create_template_files(self):
166
+ """Create template files for the web interface."""
167
+ # Create base template
168
+ base_template = """<!DOCTYPE html>
169
+ <html>
170
+ <head>
171
+ <meta charset="utf-8">
172
+ <meta name="viewport" content="width=device-width, initial-scale=1">
173
+ <title>{% block title %}OpenSCAD MCP Server{% endblock %}</title>
174
+ <link rel="stylesheet" href="/static/styles.css">
175
+ </head>
176
+ <body>
177
+ <header>
178
+ <h1>OpenSCAD MCP Server</h1>
179
+ <nav>
180
+ <ul>
181
+ <li><a href="/ui/">Home</a></li>
182
+ <li><a href="/ui/models">Models</a></li>
183
+ </ul>
184
+ </nav>
185
+ </header>
186
+
187
+ <main>
188
+ {% block content %}{% endblock %}
189
+ </main>
190
+
191
+ <footer>
192
+ <p>OpenSCAD MCP Server - Model Context Protocol Implementation</p>
193
+ </footer>
194
+
195
+ <script src="/static/script.js"></script>
196
+ </body>
197
+ </html>
198
+ """
199
+
200
+ # Create index template
201
+ index_template = """{% extends "base.html" %}
202
+
203
+ {% block title %}OpenSCAD MCP Server - Home{% endblock %}
204
+
205
+ {% block content %}
206
+ <section class="hero">
207
+ <h2>Welcome to OpenSCAD MCP Server</h2>
208
+ <p>A Model Context Protocol server for generating 3D models with OpenSCAD</p>
209
+ </section>
210
+
211
+ <section class="features">
212
+ <div class="feature">
213
+ <h3>Natural Language Processing</h3>
214
+ <p>Describe 3D objects in natural language and get parametric models</p>
215
+ </div>
216
+
217
+ <div class="feature">
218
+ <h3>Preview Generation</h3>
219
+ <p>See your models from multiple angles before exporting</p>
220
+ </div>
221
+
222
+ <div class="feature">
223
+ <h3>STL Export</h3>
224
+ <p>Generate STL files ready for 3D printing</p>
225
+ </div>
226
+ </section>
227
+ {% endblock %}
228
+ """
229
+
230
+ # Create preview template
231
+ preview_template = """{% extends "base.html" %}
232
+
233
+ {% block title %}Model Preview - {{ model_id }}{% endblock %}
234
+
235
+ {% block content %}
236
+ <section class="model-preview">
237
+ <h2>Model Preview: {{ model_id }}</h2>
238
+
239
+ <div class="preview-container">
240
+ {% for angle, url in previews.items() %}
241
+ <div class="preview-angle">
242
+ <h3>{{ angle|title }} View</h3>
243
+ <img src="{{ url }}" alt="{{ angle }} view of {{ model_id }}" class="preview-image" data-angle="{{ angle }}">
244
+ </div>
245
+ {% endfor %}
246
+ </div>
247
+
248
+ {% if stl_url %}
249
+ <div class="download-container">
250
+ <a href="{{ stl_url }}" class="download-button" download="{{ model_id }}.stl">Download STL</a>
251
+ </div>
252
+ {% endif %}
253
+ </section>
254
+ {% endblock %}
255
+ """
256
+
257
+ # Create models template
258
+ models_template = """{% extends "base.html" %}
259
+
260
+ {% block title %}All Models{% endblock %}
261
+
262
+ {% block content %}
263
+ <section class="models-list">
264
+ <h2>All Models</h2>
265
+
266
+ {% if models %}
267
+ <div class="models-grid">
268
+ {% for model in models %}
269
+ <div class="model-card">
270
+ <h3>{{ model.id }}</h3>
271
+ {% if model.preview_url %}
272
+ <img src="{{ model.preview_url }}" alt="Preview of {{ model.id }}" class="model-thumbnail">
273
+ {% else %}
274
+ <div class="no-preview">No preview available</div>
275
+ {% endif %}
276
+ <div class="model-actions">
277
+ <a href="/ui/preview/{{ model.id }}" class="view-button">View</a>
278
+ <a href="{{ model.stl_url }}" class="download-button" download="{{ model.id }}.stl">Download</a>
279
+ </div>
280
+ </div>
281
+ {% endfor %}
282
+ </div>
283
+ {% else %}
284
+ <p>No models found.</p>
285
+ {% endif %}
286
+ </section>
287
+ {% endblock %}
288
+ """
289
+
290
+ # Write templates to files
291
+ os.makedirs(self.templates_dir, exist_ok=True)
292
+
293
+ with open(os.path.join(self.templates_dir, "base.html"), "w") as f:
294
+ f.write(base_template)
295
+
296
+ with open(os.path.join(self.templates_dir, "index.html"), "w") as f:
297
+ f.write(index_template)
298
+
299
+ with open(os.path.join(self.templates_dir, "preview.html"), "w") as f:
300
+ f.write(preview_template)
301
+
302
+ with open(os.path.join(self.templates_dir, "models.html"), "w") as f:
303
+ f.write(models_template)
304
+
305
+ def _create_static_files(self):
306
+ """Create static files for the web interface."""
307
+ # Create CSS file
308
+ css = """/* Base styles */
309
+ * {
310
+ box-sizing: border-box;
311
+ margin: 0;
312
+ padding: 0;
313
+ }
314
+
315
+ body {
316
+ font-family: Arial, sans-serif;
317
+ line-height: 1.6;
318
+ color: #333;
319
+ background-color: #f4f4f4;
320
+ }
321
+
322
+ header {
323
+ background-color: #333;
324
+ color: #fff;
325
+ padding: 1rem;
326
+ }
327
+
328
+ header h1 {
329
+ margin-bottom: 0.5rem;
330
+ }
331
+
332
+ nav ul {
333
+ display: flex;
334
+ list-style: none;
335
+ }
336
+
337
+ nav ul li {
338
+ margin-right: 1rem;
339
+ }
340
+
341
+ nav ul li a {
342
+ color: #fff;
343
+ text-decoration: none;
344
+ }
345
+
346
+ nav ul li a:hover {
347
+ text-decoration: underline;
348
+ }
349
+
350
+ main {
351
+ max-width: 1200px;
352
+ margin: 0 auto;
353
+ padding: 2rem;
354
+ }
355
+
356
+ footer {
357
+ background-color: #333;
358
+ color: #fff;
359
+ text-align: center;
360
+ padding: 1rem;
361
+ margin-top: 2rem;
362
+ }
363
+
364
+ /* Home page */
365
+ .hero {
366
+ text-align: center;
367
+ margin-bottom: 2rem;
368
+ }
369
+
370
+ .hero h2 {
371
+ font-size: 2rem;
372
+ margin-bottom: 1rem;
373
+ }
374
+
375
+ .features {
376
+ display: flex;
377
+ justify-content: space-between;
378
+ flex-wrap: wrap;
379
+ }
380
+
381
+ .feature {
382
+ flex: 1;
383
+ background-color: #fff;
384
+ padding: 1.5rem;
385
+ margin: 0.5rem;
386
+ border-radius: 5px;
387
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
388
+ }
389
+
390
+ .feature h3 {
391
+ margin-bottom: 1rem;
392
+ }
393
+
394
+ /* Preview page */
395
+ .model-preview h2 {
396
+ margin-bottom: 1.5rem;
397
+ }
398
+
399
+ .preview-container {
400
+ display: flex;
401
+ flex-wrap: wrap;
402
+ gap: 1rem;
403
+ margin-bottom: 1.5rem;
404
+ }
405
+
406
+ .preview-angle {
407
+ flex: 1;
408
+ min-width: 300px;
409
+ background-color: #fff;
410
+ padding: 1rem;
411
+ border-radius: 5px;
412
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
413
+ }
414
+
415
+ .preview-angle h3 {
416
+ margin-bottom: 0.5rem;
417
+ }
418
+
419
+ .preview-image {
420
+ width: 100%;
421
+ height: auto;
422
+ border: 1px solid #ddd;
423
+ }
424
+
425
+ .download-container {
426
+ text-align: center;
427
+ margin-top: 1rem;
428
+ }
429
+
430
+ .download-button {
431
+ display: inline-block;
432
+ background-color: #4CAF50;
433
+ color: white;
434
+ padding: 0.5rem 1rem;
435
+ text-decoration: none;
436
+ border-radius: 4px;
437
+ }
438
+
439
+ .download-button:hover {
440
+ background-color: #45a049;
441
+ }
442
+
443
+ /* Models page */
444
+ .models-grid {
445
+ display: grid;
446
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
447
+ gap: 1.5rem;
448
+ }
449
+
450
+ .model-card {
451
+ background-color: #fff;
452
+ border-radius: 5px;
453
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
454
+ overflow: hidden;
455
+ }
456
+
457
+ .model-card h3 {
458
+ padding: 1rem;
459
+ background-color: #f8f8f8;
460
+ border-bottom: 1px solid #eee;
461
+ }
462
+
463
+ .model-thumbnail {
464
+ width: 100%;
465
+ height: 200px;
466
+ object-fit: contain;
467
+ background-color: #f4f4f4;
468
+ }
469
+
470
+ .no-preview {
471
+ width: 100%;
472
+ height: 200px;
473
+ display: flex;
474
+ align-items: center;
475
+ justify-content: center;
476
+ background-color: #f4f4f4;
477
+ color: #999;
478
+ }
479
+
480
+ .model-actions {
481
+ display: flex;
482
+ padding: 1rem;
483
+ }
484
+
485
+ .view-button, .download-button {
486
+ flex: 1;
487
+ text-align: center;
488
+ padding: 0.5rem;
489
+ text-decoration: none;
490
+ border-radius: 4px;
491
+ margin: 0 0.25rem;
492
+ }
493
+
494
+ .view-button {
495
+ background-color: #2196F3;
496
+ color: white;
497
+ }
498
+
499
+ .view-button:hover {
500
+ background-color: #0b7dda;
501
+ }
502
+ """
503
+
504
+ # Create JavaScript file
505
+ js = """// JavaScript for OpenSCAD MCP Server web interface
506
+
507
+ document.addEventListener('DOMContentLoaded', function() {
508
+ // Handle image loading errors
509
+ const images = document.querySelectorAll('img');
510
+ images.forEach(img => {
511
+ img.onerror = function() {
512
+ this.src = '/static/placeholder.png';
513
+ };
514
+ });
515
+
516
+ // Handle STL download
517
+ const downloadButtons = document.querySelectorAll('.download-button');
518
+ downloadButtons.forEach(button => {
519
+ button.addEventListener('click', async function(e) {
520
+ e.preventDefault();
521
+
522
+ const url = this.getAttribute('href');
523
+ const filename = this.hasAttribute('download') ? this.getAttribute('download') : 'model.stl';
524
+
525
+ try {
526
+ const response = await fetch(url);
527
+ const data = await response.json();
528
+
529
+ // Decode base64 content
530
+ const content = atob(data.content);
531
+
532
+ // Convert to Blob
533
+ const bytes = new Uint8Array(content.length);
534
+ for (let i = 0; i < content.length; i++) {
535
+ bytes[i] = content.charCodeAt(i);
536
+ }
537
+ const blob = new Blob([bytes], { type: data.content_type });
538
+
539
+ // Create download link
540
+ const downloadLink = document.createElement('a');
541
+ downloadLink.href = URL.createObjectURL(blob);
542
+ downloadLink.download = data.filename || filename;
543
+
544
+ // Trigger download
545
+ document.body.appendChild(downloadLink);
546
+ downloadLink.click();
547
+ document.body.removeChild(downloadLink);
548
+ } catch (error) {
549
+ console.error('Error downloading file:', error);
550
+ alert('Error downloading file. Please try again.');
551
+ }
552
+ });
553
+ });
554
+
555
+ // Handle preview images
556
+ const previewImages = document.querySelectorAll('.preview-image');
557
+ previewImages.forEach(img => {
558
+ img.addEventListener('click', function() {
559
+ const url = this.getAttribute('src');
560
+ const angle = this.getAttribute('data-angle');
561
+
562
+ // Create modal for larger view
563
+ const modal = document.createElement('div');
564
+ modal.className = 'preview-modal';
565
+ modal.innerHTML = `
566
+ <div class="modal-content">
567
+ <span class="close-button">&times;</span>
568
+ <h3>${angle ? angle.charAt(0).toUpperCase() + angle.slice(1) + ' View' : 'Preview'}</h3>
569
+ <img src="${url}" alt="Preview">
570
+ </div>
571
+ `;
572
+
573
+ // Add modal styles
574
+ modal.style.position = 'fixed';
575
+ modal.style.top = '0';
576
+ modal.style.left = '0';
577
+ modal.style.width = '100%';
578
+ modal.style.height = '100%';
579
+ modal.style.backgroundColor = 'rgba(0,0,0,0.7)';
580
+ modal.style.display = 'flex';
581
+ modal.style.alignItems = 'center';
582
+ modal.style.justifyContent = 'center';
583
+ modal.style.zIndex = '1000';
584
+
585
+ const modalContent = modal.querySelector('.modal-content');
586
+ modalContent.style.backgroundColor = '#fff';
587
+ modalContent.style.padding = '20px';
588
+ modalContent.style.borderRadius = '5px';
589
+ modalContent.style.maxWidth = '90%';
590
+ modalContent.style.maxHeight = '90%';
591
+ modalContent.style.overflow = 'auto';
592
+
593
+ const closeButton = modal.querySelector('.close-button');
594
+ closeButton.style.float = 'right';
595
+ closeButton.style.fontSize = '1.5rem';
596
+ closeButton.style.fontWeight = 'bold';
597
+ closeButton.style.cursor = 'pointer';
598
+
599
+ const modalImg = modal.querySelector('img');
600
+ modalImg.style.maxWidth = '100%';
601
+ modalImg.style.maxHeight = '70vh';
602
+ modalImg.style.display = 'block';
603
+ modalImg.style.margin = '0 auto';
604
+
605
+ // Add modal to body
606
+ document.body.appendChild(modal);
607
+
608
+ // Close modal when clicking close button or outside the modal
609
+ closeButton.addEventListener('click', function() {
610
+ document.body.removeChild(modal);
611
+ });
612
+
613
+ modal.addEventListener('click', function(e) {
614
+ if (e.target === modal) {
615
+ document.body.removeChild(modal);
616
+ }
617
+ });
618
+ });
619
+ });
620
+ });
621
+ """
622
+
623
+ # Create placeholder image
624
+ placeholder_svg = """<svg xmlns="http://www.w3.org/2000/svg" width="800" height="600" viewBox="0 0 800 600">
625
+ <rect width="800" height="600" fill="#f0f0f0"/>
626
+ <text x="400" y="300" font-family="Arial" font-size="24" text-anchor="middle" fill="#999">Preview not available</text>
627
+ </svg>"""
628
+
629
+ # Write static files
630
+ os.makedirs(self.static_dir, exist_ok=True)
631
+
632
+ with open(os.path.join(self.static_dir, "styles.css"), "w") as f:
633
+ f.write(css)
634
+
635
+ with open(os.path.join(self.static_dir, "script.js"), "w") as f:
636
+ f.write(js)
637
+
638
+ with open(os.path.join(self.static_dir, "placeholder.svg"), "w") as f:
639
+ f.write(placeholder_svg)