@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,308 @@
1
+ """PCB layout and routing tools."""
2
+
3
+ import re
4
+ import uuid
5
+ from pathlib import Path
6
+
7
+ from ..server import mcp
8
+ from ..utils.kicad_cli import run_kicad_cli
9
+ from ..utils.kicad_version import get_pcb_version
10
+
11
+ _KICAD_CLI_MISSING_MSG = """⚠️ kicad-cli not found (PATH or KiCad install directory).
12
+
13
+ Gerber export requires KiCad's command-line tools.
14
+
15
+ Please:
16
+ 1. Install KiCad 7+ (https://www.kicad.org/)
17
+ 2. Ensure kicad-cli is in system PATH, or set the KICAD_CLI
18
+ environment variable to its full path
19
+ (on Windows typically C:\\Program Files\\KiCad\\<ver>\\bin\\kicad-cli.exe)
20
+
21
+ **Manual export:**
22
+ Open the board in KiCad's PCB editor → File → Fabrication Outputs → Gerbers.
23
+ """
24
+
25
+
26
+ @mcp.tool()
27
+ async def setup_pcb_layout(
28
+ schematic_path: str,
29
+ width: float = 100.0,
30
+ height: float = 100.0,
31
+ unit: str = "mm",
32
+ ) -> str:
33
+ """Initialize an empty PCB with the specified board outline dimensions.
34
+
35
+ Writes a .kicad_pcb next to the schematic containing only the board
36
+ outline (Edge.Cuts), a standard 2-layer stack and default design rules —
37
+ no footprints, tracks or nets. KiCad stores .kicad_pcb coordinates in
38
+ millimetres (plain decimals), so dimensions are emitted directly in mm.
39
+
40
+ Args:
41
+ schematic_path: Path to the .kicad_sch the board is based on (used to
42
+ derive the .kicad_pcb path and board title).
43
+ width: Board width in the given unit.
44
+ height: Board height in the given unit.
45
+ unit: "mm" (default) or "mil".
46
+
47
+ Returns:
48
+ Confirmation reporting the actual outline size, layer count and
49
+ element counts read back from the written file.
50
+ """
51
+ try:
52
+ sch_path = Path(schematic_path)
53
+ if not sch_path.exists():
54
+ return f"❌ Schematic file not found: {schematic_path}"
55
+
56
+ pcb_path = sch_path.with_suffix(".kicad_pcb")
57
+
58
+ # KiCad .kicad_pcb coordinates are millimetres (plain decimals), NOT
59
+ # nanometres — multiplying by 1e6 wrote ~100 km outlines (issue #17).
60
+ if unit == "mil":
61
+ w = width * 0.0254
62
+ h = height * 0.0254
63
+ else:
64
+ w = float(width)
65
+ h = float(height)
66
+
67
+ pcb_version = get_pcb_version()
68
+
69
+ # KiCad 9 layer numbering (from the bundled template): copper layers
70
+ # use the modern odd-pair scheme (B.Cu = 2, not 31) and Edge.Cuts is
71
+ # layer 25 (not the KiCad-5 value 44). The old numbers made KiCad 9
72
+ # read the stack back as a single layer.
73
+ pcb_content = f'''(kicad_pcb (version {pcb_version}) (generator "kicad-mcp-server")
74
+
75
+ (general
76
+ (thickness 1.6)
77
+ (legacy_thru_hole_to_restricted yes)
78
+ )
79
+
80
+ (paper "A4")
81
+
82
+ (title_block
83
+ (title "{sch_path.stem}")
84
+ )
85
+
86
+ (layers
87
+ (0 "F.Cu" signal)
88
+ (2 "B.Cu" signal)
89
+ (9 "F.Adhes" user "F.Adhesive")
90
+ (11 "B.Adhes" user "B.Adhesive")
91
+ (13 "F.Paste" user)
92
+ (15 "B.Paste" user)
93
+ (5 "F.SilkS" user "F.Silkscreen")
94
+ (7 "B.SilkS" user "B.Silkscreen")
95
+ (1 "F.Mask" user)
96
+ (3 "B.Mask" user)
97
+ (17 "Dwgs.User" user "User.Drawings")
98
+ (19 "Cmts.User" user "User.Comments")
99
+ (21 "Eco1.User" user "User.Eco1")
100
+ (23 "Eco2.User" user "User.Eco2")
101
+ (25 "Edge.Cuts" user)
102
+ (27 "Margin" user)
103
+ (31 "F.CrtYd" user "F.Courtyard")
104
+ (29 "B.CrtYd" user "B.Courtyard")
105
+ (35 "F.Fab" user)
106
+ (33 "B.Fab" user)
107
+ )
108
+
109
+ (setup
110
+ (pad_to_mask_clearance 0)
111
+ (aux_axis_origin 0 0)
112
+ (grid_origin 0 0)
113
+ (pcbplotparams
114
+ (layerselection 0x00010fc_ffffffff)
115
+ (plot_on_all_layers_selection 0x0000000_00000000)
116
+ (disableapertmacros false)
117
+ (usegerberextensions false)
118
+ (usegerberattributes true)
119
+ (usegerberadvancedattributes true)
120
+ (creategerberjobfile true)
121
+ (excludeedgelayer true)
122
+ (linewidth 0.100000)
123
+ (plotframeref false)
124
+ (viasonmask false)
125
+ (mode 1)
126
+ (useauxorigin false)
127
+ (hpglpennumber 1)
128
+ (hpglpenspeed 20)
129
+ (hpglpendiameter 15.000000)
130
+ (dxfpolygonmode true)
131
+ (dxfimperialunits true)
132
+ (dxfusepcbnewfont true)
133
+ (psnegative false)
134
+ (psa4output false)
135
+ (plotreference true)
136
+ (plotvalue true)
137
+ (plotinvisibletext false)
138
+ (sketchpadsonfab false)
139
+ (subtractmaskfromsilk false)
140
+ (outputformat 1)
141
+ (mirror false)
142
+ (drillshape 0)
143
+ (scaleselection 1)
144
+ (outputdirectory ""))
145
+ )
146
+
147
+ (net 0 "")
148
+
149
+ (gr_line (start 0 0) (end {w} 0)
150
+ (stroke (width 0.15) (type default))
151
+ (layer "Edge.Cuts") (uuid "{uuid.uuid4()}"))
152
+ (gr_line (start {w} 0) (end {w} {h})
153
+ (stroke (width 0.15) (type default))
154
+ (layer "Edge.Cuts") (uuid "{uuid.uuid4()}"))
155
+ (gr_line (start {w} {h}) (end 0 {h})
156
+ (stroke (width 0.15) (type default))
157
+ (layer "Edge.Cuts") (uuid "{uuid.uuid4()}"))
158
+ (gr_line (start 0 {h}) (end 0 0)
159
+ (stroke (width 0.15) (type default))
160
+ (layer "Edge.Cuts") (uuid "{uuid.uuid4()}"))
161
+
162
+ )
163
+ '''
164
+
165
+ pcb_path.write_text(pcb_content, encoding="utf-8")
166
+
167
+ # Report what is actually in the file, never features the write did
168
+ # not produce (issue #17).
169
+ report = _summarize_pcb(pcb_path)
170
+ return f"""✅ PCB layout initialized.
171
+
172
+ **Schematic:** {schematic_path}
173
+ **PCB File:** {pcb_path}
174
+
175
+ {report}
176
+
177
+ Next steps:
178
+ 1. Import the netlist from the schematic (Update PCB from schematic)
179
+ 2. Place footprints and route connections
180
+ 3. Run DRC, then export Gerbers (export_gerber)
181
+ """
182
+
183
+ except Exception as e:
184
+ import traceback
185
+ return f"❌ Error setting up PCB layout: {e}\n\n{traceback.format_exc()}"
186
+
187
+
188
+ def _summarize_pcb(pcb_path: Path) -> str:
189
+ """Read a .kicad_pcb back and report its actual outline / layers / elements.
190
+
191
+ For a freshly-initialised board the only items carrying start/end coords
192
+ are the four Edge.Cuts gr_lines, so the min/max of those coords is the
193
+ exact board outline.
194
+ """
195
+ text = pcb_path.read_text(encoding="utf-8")
196
+
197
+ coords = [
198
+ (float(mx), float(my))
199
+ for mx, my in re.findall(
200
+ r"\((?:start|end)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\)", text
201
+ )
202
+ ]
203
+ if coords:
204
+ xs = [c[0] for c in coords]
205
+ ys = [c[1] for c in coords]
206
+ outline = f"{max(xs) - min(xs):.3f} × {max(ys) - min(ys):.3f} mm"
207
+ else:
208
+ outline = "(could not parse Edge.Cuts outline)"
209
+
210
+ layer_count = len(re.findall(r'^\s*\(\d+\s+"', text, re.MULTILINE))
211
+ n_footprints = len(re.findall(r'\(footprint\s+"', text))
212
+ n_segments = len(re.findall(r'\(segment\s', text))
213
+ n_nets = len(re.findall(r'\(net\s+\d+\s+"', text))
214
+
215
+ return (
216
+ f"**Dimensions (read back):** {outline}\n"
217
+ f"**Layers:** {layer_count}\n"
218
+ f"**Elements:** {n_footprints} footprint(s), "
219
+ f"{n_segments} track(s), {n_nets} net(s) declared"
220
+ )
221
+
222
+
223
+ @mcp.tool()
224
+ async def export_gerber(
225
+ pcb_path: str,
226
+ output_dir: str = "",
227
+ ) -> str:
228
+ """Export a PCB to Gerber + drill files for fabrication via kicad-cli.
229
+
230
+ Runs KiCad's command-line exporter against the .kicad_pcb and reports the
231
+ files actually produced. Requires KiCad 7+ installed with kicad-cli on PATH.
232
+
233
+ Args:
234
+ pcb_path: Path to the .kicad_pcb file.
235
+ output_dir: Directory for output (default: a "gerber" folder next to
236
+ the PCB).
237
+
238
+ Returns:
239
+ Confirmation listing the generated files, or an actionable error.
240
+ """
241
+ try:
242
+ pcb = Path(pcb_path)
243
+ if not pcb.exists():
244
+ return f"❌ PCB file not found: {pcb_path}"
245
+
246
+ out_path = Path(output_dir) if output_dir else pcb.parent / "gerber"
247
+ out_path.mkdir(parents=True, exist_ok=True)
248
+ # Track name + mtime: a re-export rewrites the same files, and a pure
249
+ # name diff would then look like "nothing was produced".
250
+ before = {p.name: p.stat().st_mtime_ns for p in out_path.iterdir()}
251
+
252
+ async def _run(args: list[str]) -> tuple[int, str]:
253
+ r = await run_kicad_cli(args, timeout=180)
254
+ return r.returncode, (r.stderr or b"").decode("utf-8", "replace").strip()
255
+
256
+ # Gerbers first (required); drill is best-effort afterwards. Note the
257
+ # subcommand is "gerbers" (plural) and the output flag is "-o", not
258
+ # "--output-dir" — KiCad 7-10 all use this form. With no --layers it
259
+ # plots every layer defined in the board, so it generalises to N-layer.
260
+ try:
261
+ rc, err = await _run(["pcb", "export", "gerbers", "-o", str(out_path), str(pcb)])
262
+ except FileNotFoundError:
263
+ return _KICAD_CLI_MISSING_MSG
264
+ if rc != 0:
265
+ return (
266
+ f"❌ Gerber export failed (kicad-cli rc={rc}):\n\n"
267
+ f"{err or '(no stderr)'}"
268
+ )
269
+
270
+ drill_note = ""
271
+ try:
272
+ rc2, err2 = await _run(["pcb", "export", "drill", "-o", str(out_path), str(pcb)])
273
+ if rc2 != 0:
274
+ drill_note = (
275
+ f"\n\n⚠️ Drill export returned rc={rc2}: "
276
+ f"{err2 or '(no stderr)'} (Gerbers still produced)"
277
+ )
278
+ except FileNotFoundError:
279
+ # kicad-cli was available for gerber, so this shouldn't happen —
280
+ # surface it rather than swallow.
281
+ drill_note = "\n\n⚠️ kicad-cli unavailable for drill export."
282
+
283
+ new_files = sorted(
284
+ p.name
285
+ for p in out_path.iterdir()
286
+ if p.name not in before or p.stat().st_mtime_ns != before[p.name]
287
+ )
288
+ if not new_files:
289
+ return (
290
+ f"⚠️ kicad-cli reported success but produced no files in "
291
+ f"{out_path}.{drill_note}"
292
+ )
293
+
294
+ listing = "\n".join(f"- {n}" for n in new_files)
295
+ return f"""✅ Gerber export complete.
296
+
297
+ **PCB:** {pcb_path}
298
+ **Output directory:** {out_path}
299
+
300
+ **Files generated ({len(new_files)}):**
301
+ {listing}{drill_note}
302
+
303
+ Send all of these to your fabricator (e.g. JLCPCB, PCBWay, OSH Park).
304
+ """
305
+
306
+ except Exception as e:
307
+ import traceback
308
+ return f"❌ Error exporting Gerber: {e}\n\n{traceback.format_exc()}"