@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.
- package/README.md +116 -0
- package/config/mcp/antigravity.json +22 -0
- package/config/mcp/claude.json +22 -0
- package/config/mcp/codex.toml +25 -0
- package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
- package/docs/review_documentation.md +121 -0
- package/installer.js +358 -0
- package/mcp_servers/kicad-mcp-server/.env.example +22 -0
- package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
- package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
- package/mcp_servers/kicad-mcp-server/README.md +316 -0
- package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
- package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
- package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
- package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
- package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
- package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
- package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
- package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
- package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
- package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
- package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
- package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
- package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
- package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
- package/mcp_servers/openscad-mcp-server/README.md +154 -0
- package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
- package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
- package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
- package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
- package/mcp_servers/openscad-mcp-server/glama.json +4 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
- package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
- package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
- package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
- package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
- package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
- package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
- package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
- package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
- package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
- package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
- package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
- package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
- package/package.json +44 -0
- package/scripts/install_mcps.sh +86 -0
- package/scripts/openscad_wrapper.sh +62 -0
- package/scripts/run_kicad_mcp.sh +25 -0
- package/scripts/run_openscad_mcp.sh +41 -0
- package/scripts/test_mcp_connection.py +626 -0
- package/scripts/test_mcp_connection.sh +168 -0
- package/skills/code-first-hardware-design/SKILL.md +260 -0
- package/skills/pcb-constraint-definition/SKILL.md +236 -0
- package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
- package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
- package/skills/schematic-datasheet-analysis/SKILL.md +202 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""KiCad project creation tools for KiCad MCP Server - Updated for KiCad 9.0+
|
|
2
|
+
|
|
3
|
+
Uses KiCad's template projects to ensure 100% compatibility.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from ..server import mcp
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _find_kicad_template() -> Path | None:
|
|
17
|
+
"""Find KiCad template directory, auto-detecting installed version."""
|
|
18
|
+
from ..utils.kicad_version import find_kicad_install
|
|
19
|
+
|
|
20
|
+
kicad = find_kicad_install()
|
|
21
|
+
if kicad:
|
|
22
|
+
install_path, _version = kicad
|
|
23
|
+
for template_name in ["Arduino_Mega", "EuroCard160mmX100mm"]:
|
|
24
|
+
p = install_path / "share" / "kicad" / "template" / template_name
|
|
25
|
+
if p.exists():
|
|
26
|
+
return p
|
|
27
|
+
|
|
28
|
+
# macOS fallback
|
|
29
|
+
mac_base = Path("/Applications/KiCad/KiCad.app/Contents/SharedSupport/template")
|
|
30
|
+
for t in ["Arduino_Mega", "EuroCard160mmX100mm"]:
|
|
31
|
+
if (mac_base / t).exists():
|
|
32
|
+
return mac_base / t
|
|
33
|
+
|
|
34
|
+
# Linux fallback
|
|
35
|
+
for base in [Path("/usr/share/kicad/template"), Path("/usr/local/share/kicad/template")]:
|
|
36
|
+
for t in ["Arduino_Mega", "EuroCard160mmX100mm"]:
|
|
37
|
+
if (base / t).exists():
|
|
38
|
+
return base / t
|
|
39
|
+
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_date_string() -> str:
|
|
44
|
+
"""Get current date in ISO format."""
|
|
45
|
+
return datetime.now().strftime("%Y-%m-%d")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@mcp.tool()
|
|
49
|
+
async def create_kicad_project(
|
|
50
|
+
project_path: str,
|
|
51
|
+
project_name: str,
|
|
52
|
+
title: str = "",
|
|
53
|
+
company: str = "",
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Create a complete KiCad 9.0+ project by copying KiCad's template.
|
|
56
|
+
|
|
57
|
+
This method copies a KiCad template project and modifies it,
|
|
58
|
+
ensuring 100% compatibility with KiCad 9.0+.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
project_path: Directory path for the project
|
|
62
|
+
project_name: Name of the project (without extension)
|
|
63
|
+
title: Optional project title
|
|
64
|
+
company: Optional company name
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Confirmation message with created files
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
# Find KiCad template
|
|
71
|
+
template_path = _find_kicad_template()
|
|
72
|
+
|
|
73
|
+
if not template_path:
|
|
74
|
+
return """❌ KiCad template not found.
|
|
75
|
+
|
|
76
|
+
Please ensure KiCad is installed:
|
|
77
|
+
macOS: brew install --cask kicad
|
|
78
|
+
Linux: sudo apt install kicad
|
|
79
|
+
Windows: https://www.kicad.org/download/
|
|
80
|
+
|
|
81
|
+
After installation, this tool will use KiCad's template to create projects.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
path = Path(project_path)
|
|
85
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
|
|
87
|
+
date_str = _get_date_string()
|
|
88
|
+
title_text = title or project_name
|
|
89
|
+
|
|
90
|
+
# Copy all files from template
|
|
91
|
+
for file in template_path.glob("*"):
|
|
92
|
+
if file.is_file():
|
|
93
|
+
dest = path / file.name.replace("Arduino_Mega", project_name).replace("EuroCard160mmX100mm", project_name)
|
|
94
|
+
shutil.copy(file, dest)
|
|
95
|
+
|
|
96
|
+
# Rename files if needed
|
|
97
|
+
for old_file in path.glob("*"):
|
|
98
|
+
if "Arduino_Mega" in old_file.name or "EuroCard160mmX100mm" in old_file.name:
|
|
99
|
+
new_name = old_file.name.replace("Arduino_Mega", project_name).replace("EuroCard160mmX100mm", project_name)
|
|
100
|
+
new_path = old_file.parent / new_name
|
|
101
|
+
if old_file != new_path:
|
|
102
|
+
old_file.rename(new_path)
|
|
103
|
+
|
|
104
|
+
# Modify .kicad_pro file
|
|
105
|
+
pro_file = path / f"{project_name}.kicad_pro"
|
|
106
|
+
|
|
107
|
+
if pro_file.exists():
|
|
108
|
+
with open(pro_file) as f:
|
|
109
|
+
pro_data = json.load(f)
|
|
110
|
+
|
|
111
|
+
# Update metadata
|
|
112
|
+
pro_data["meta"]["filename"] = f"{project_name}.kicad_pro"
|
|
113
|
+
|
|
114
|
+
# Update sheets
|
|
115
|
+
if "sheets" in pro_data and pro_data["sheets"]:
|
|
116
|
+
new_uuid = str(uuid.uuid4())
|
|
117
|
+
pro_data["sheets"] = [[new_uuid, "Root"]]
|
|
118
|
+
|
|
119
|
+
with open(pro_file, 'w') as f:
|
|
120
|
+
json.dump(pro_data, f, indent=2)
|
|
121
|
+
|
|
122
|
+
# Modify .kicad_sch file
|
|
123
|
+
sch_file = path / f"{project_name}.kicad_sch"
|
|
124
|
+
|
|
125
|
+
if sch_file.exists():
|
|
126
|
+
content = sch_file.read_text()
|
|
127
|
+
|
|
128
|
+
# Update UUID
|
|
129
|
+
new_uuid = str(uuid.uuid4())
|
|
130
|
+
content = re.sub(r'\(uuid "([^"]*)"\)', f'(uuid "{new_uuid}")', content, count=1)
|
|
131
|
+
|
|
132
|
+
# Update title block. Templates may not contain (title "...") or
|
|
133
|
+
# (company "...") fields, so insert them into (title_block ...) when
|
|
134
|
+
# absent — otherwise the regex silently no-ops while the success
|
|
135
|
+
# message still claims the title/company was written.
|
|
136
|
+
if title_text:
|
|
137
|
+
if '(title "' in content:
|
|
138
|
+
content = re.sub(r'\(title "([^"]*)"\)', f'(title "{title_text}")', content, count=1)
|
|
139
|
+
else:
|
|
140
|
+
content = content.replace("(title_block", f'(title_block\n (title "{title_text}")', 1)
|
|
141
|
+
if '(date "' in content:
|
|
142
|
+
content = re.sub(r'\(date "([^"]*)"\)', f'(date "{date_str}")', content, count=1)
|
|
143
|
+
if company:
|
|
144
|
+
if '(company "' in content:
|
|
145
|
+
content = re.sub(r'\(company "([^"]*)"\)', f'(company "{company}")', content, count=1)
|
|
146
|
+
else:
|
|
147
|
+
content = content.replace("(title_block", f'(title_block\n (company "{company}")', 1)
|
|
148
|
+
|
|
149
|
+
sch_file.write_text(content)
|
|
150
|
+
|
|
151
|
+
# List files that were actually copied (templates vary — don't hardcode
|
|
152
|
+
# "fp-lib-table", which some templates don't ship).
|
|
153
|
+
created_files = sorted(p.name for p in path.iterdir() if p.is_file())
|
|
154
|
+
files_listing = "\n".join(f"- {n}" for n in created_files)
|
|
155
|
+
|
|
156
|
+
return f"""# ✅ KiCad 9.0+ Project Created Successfully!
|
|
157
|
+
|
|
158
|
+
**Project Path:** {path}
|
|
159
|
+
**Project Name:** {project_name}
|
|
160
|
+
**Title:** {title_text}
|
|
161
|
+
**Company:** {company or '(none)'}
|
|
162
|
+
**Template Used:** {template_path.name}
|
|
163
|
+
|
|
164
|
+
## 📄 Files Created:
|
|
165
|
+
|
|
166
|
+
{files_listing}
|
|
167
|
+
|
|
168
|
+
## 📖 How to Open in KiCad 9.0+:
|
|
169
|
+
|
|
170
|
+
1. Open KiCad
|
|
171
|
+
2. File → Open Project...
|
|
172
|
+
3. Navigate to: {pro_file}
|
|
173
|
+
4. Click Open
|
|
174
|
+
|
|
175
|
+
The project will open in KiCad 9.0+ **without** version warnings!
|
|
176
|
+
|
|
177
|
+
## ✅ Compatibility:
|
|
178
|
+
|
|
179
|
+
- Created from KiCad's official template
|
|
180
|
+
- 100% compatible with KiCad 9.0+
|
|
181
|
+
- All settings and configurations are valid
|
|
182
|
+
- Ready for schematic design and PCB layout
|
|
183
|
+
|
|
184
|
+
## 🔧 Next Steps:
|
|
185
|
+
|
|
186
|
+
1. Open schematic editor to add components
|
|
187
|
+
2. Use `add_component_from_library` to add parts
|
|
188
|
+
3. Use `add_wire` and `add_global_label` for connections
|
|
189
|
+
4. Update PCB from schematic when ready
|
|
190
|
+
|
|
191
|
+
Project is ready for KiCad 9.0+! 🚀
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
except Exception as e:
|
|
195
|
+
import traceback
|
|
196
|
+
return f"Error creating project: {e}\n\n{traceback.format_exc()}"
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"""Schematic analysis tools for KiCad MCP Server."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ..parsers.schematic_parser import SchematicParser
|
|
6
|
+
from ..server import mcp
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@mcp.tool()
|
|
10
|
+
async def list_schematic_components(
|
|
11
|
+
file_path: str,
|
|
12
|
+
filter_type: str | None = None,
|
|
13
|
+
filter_value: str | None = None,
|
|
14
|
+
filter_dnp: bool | None = None,
|
|
15
|
+
) -> str:
|
|
16
|
+
"""List all components in a KiCad schematic file.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
file_path: Path to .kicad_sch file
|
|
20
|
+
filter_type: Optional filter by component type prefix (e.g., 'R', 'C', 'U', 'IC')
|
|
21
|
+
filter_value: Optional filter by component value (partial match)
|
|
22
|
+
filter_dnp: Optional filter by DNP flag (True=only DNP, False=only non-DNP)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Formatted list of components with their properties
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
parser = SchematicParser(file_path)
|
|
29
|
+
components = parser.get_components()
|
|
30
|
+
|
|
31
|
+
# Apply filters
|
|
32
|
+
if filter_type:
|
|
33
|
+
components = [c for c in components if c.reference.startswith(filter_type.upper())]
|
|
34
|
+
|
|
35
|
+
if filter_value:
|
|
36
|
+
components = [c for c in components if filter_value.lower() in c.value.lower()]
|
|
37
|
+
|
|
38
|
+
if filter_dnp is not None:
|
|
39
|
+
components = [c for c in components if c.flags.get("dnp", False) == filter_dnp]
|
|
40
|
+
|
|
41
|
+
if not components:
|
|
42
|
+
return "No components found matching the specified criteria."
|
|
43
|
+
|
|
44
|
+
# Check if any non-default flags exist
|
|
45
|
+
has_dnp = any(c.flags.get("dnp", False) for c in components)
|
|
46
|
+
has_not_in_bom = any(not c.flags.get("in_bom", True) for c in components)
|
|
47
|
+
|
|
48
|
+
# Format output
|
|
49
|
+
header = "| Reference | Value | Footprint | Library |"
|
|
50
|
+
separator = "|-----------|-------|-----------|---------|"
|
|
51
|
+
if has_dnp or has_not_in_bom:
|
|
52
|
+
header += " DNP | In BOM |"
|
|
53
|
+
separator += "-----|--------|"
|
|
54
|
+
|
|
55
|
+
lines = [
|
|
56
|
+
f"# Components in {file_path}",
|
|
57
|
+
f"Total: {len(components)} component(s)",
|
|
58
|
+
"",
|
|
59
|
+
header,
|
|
60
|
+
separator,
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
for comp in components:
|
|
64
|
+
footprint = comp.footprint or "-"
|
|
65
|
+
library = comp.library_id.split(":")[-1] if ":" in comp.library_id else comp.library_id
|
|
66
|
+
row = f"| {comp.reference} | {comp.value} | {footprint} | {library} |"
|
|
67
|
+
if has_dnp or has_not_in_bom:
|
|
68
|
+
dnp = "Yes" if comp.flags.get("dnp", False) else ""
|
|
69
|
+
in_bom = "No" if not comp.flags.get("in_bom", True) else ""
|
|
70
|
+
row += f" {dnp} | {in_bom} |"
|
|
71
|
+
lines.append(row)
|
|
72
|
+
|
|
73
|
+
return "\n".join(lines)
|
|
74
|
+
|
|
75
|
+
except FileNotFoundError as e:
|
|
76
|
+
return f"Error: {e}"
|
|
77
|
+
except Exception as e:
|
|
78
|
+
return f"Error parsing schematic: {e}"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@mcp.tool()
|
|
82
|
+
async def get_symbol_details(file_path: str, reference: str) -> str:
|
|
83
|
+
"""Get detailed information about a specific symbol/component.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
file_path: Path to .kicad_sch file
|
|
87
|
+
reference: Component reference designator (e.g., 'R1', 'U1')
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Detailed component information including pins and properties
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
parser = SchematicParser(file_path)
|
|
94
|
+
component = parser.get_component_by_reference(reference)
|
|
95
|
+
|
|
96
|
+
if not component:
|
|
97
|
+
return f"Component '{reference}' not found in schematic."
|
|
98
|
+
|
|
99
|
+
# Format output
|
|
100
|
+
lines = [
|
|
101
|
+
f"# Symbol Details: {component.reference}",
|
|
102
|
+
"",
|
|
103
|
+
f"**Value:** {component.value}",
|
|
104
|
+
f"**Library:** {component.library_id}",
|
|
105
|
+
f"**Footprint:** {component.footprint or 'Not assigned'}",
|
|
106
|
+
f"**Position:** ({component.position[0]:.2f}, {component.position[1]:.2f})",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
if component.unit:
|
|
110
|
+
lines.append(f"**Unit:** {component.unit}")
|
|
111
|
+
|
|
112
|
+
# Properties
|
|
113
|
+
if component.properties:
|
|
114
|
+
lines.append("")
|
|
115
|
+
lines.append("## Properties")
|
|
116
|
+
for key, value in sorted(component.properties.items()):
|
|
117
|
+
if key not in ("Value", "Footprint"):
|
|
118
|
+
lines.append(f"- {key}: {value}")
|
|
119
|
+
|
|
120
|
+
# Pins
|
|
121
|
+
if component.pins:
|
|
122
|
+
lines.append("")
|
|
123
|
+
lines.append("## Pins")
|
|
124
|
+
lines.append("| Pin | Name | Type |")
|
|
125
|
+
lines.append("|-----|------|------|")
|
|
126
|
+
for pin in component.pins:
|
|
127
|
+
pin_num = pin.get("number", "?")
|
|
128
|
+
pin_name = pin.get("name", "")
|
|
129
|
+
pin_type = pin.get("electrical_type", "")
|
|
130
|
+
lines.append(f"| {pin_num} | {pin_name} | {pin_type} |")
|
|
131
|
+
|
|
132
|
+
return "\n".join(lines)
|
|
133
|
+
|
|
134
|
+
except FileNotFoundError as e:
|
|
135
|
+
return f"Error: {e}"
|
|
136
|
+
except Exception as e:
|
|
137
|
+
return f"Error parsing schematic: {e}"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@mcp.tool()
|
|
141
|
+
async def search_symbols(
|
|
142
|
+
file_path: str,
|
|
143
|
+
pattern: str,
|
|
144
|
+
search_fields: str = "all",
|
|
145
|
+
) -> str:
|
|
146
|
+
"""Search for symbols/components matching a pattern.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
file_path: Path to .kicad_sch file
|
|
150
|
+
pattern: Search pattern (supports regex)
|
|
151
|
+
search_fields: Fields to search: 'all', 'reference', 'value', 'library'
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of matching components
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
parser = SchematicParser(file_path)
|
|
158
|
+
components = parser.search_components(pattern)
|
|
159
|
+
|
|
160
|
+
if not components:
|
|
161
|
+
return f"No components found matching pattern: {pattern}"
|
|
162
|
+
|
|
163
|
+
# Format output
|
|
164
|
+
lines = [
|
|
165
|
+
f"# Search Results: '{pattern}'",
|
|
166
|
+
f"Found {len(components)} matching component(s)",
|
|
167
|
+
"",
|
|
168
|
+
"| Reference | Value | Library |",
|
|
169
|
+
"|-----------|-------|---------|",
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
for comp in components[:50]: # Limit to 50 results
|
|
173
|
+
library = comp.library_id.split(":")[-1] if ":" in comp.library_id else comp.library_id
|
|
174
|
+
lines.append(f"| {comp.reference} | {comp.value} | {library} |")
|
|
175
|
+
|
|
176
|
+
if len(components) > 50:
|
|
177
|
+
lines.append(f"\n... and {len(components) - 50} more")
|
|
178
|
+
|
|
179
|
+
return "\n".join(lines)
|
|
180
|
+
|
|
181
|
+
except FileNotFoundError as e:
|
|
182
|
+
return f"Error: {e}"
|
|
183
|
+
except Exception as e:
|
|
184
|
+
return f"Error parsing schematic: {e}"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@mcp.tool()
|
|
188
|
+
async def list_schematic_nets(
|
|
189
|
+
file_path: str,
|
|
190
|
+
filter_power: bool = False,
|
|
191
|
+
) -> str:
|
|
192
|
+
"""List all nets in a KiCad schematic.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
file_path: Path to .kicad_sch file
|
|
196
|
+
filter_power: If True, only show power nets (VCC, GND, etc.)
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Formatted list of nets
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
parser = SchematicParser(file_path)
|
|
203
|
+
nets = parser.get_nets()
|
|
204
|
+
|
|
205
|
+
# Filter for power nets if requested
|
|
206
|
+
if filter_power:
|
|
207
|
+
power_keywords = ["gnd", "vcc", "vdd", "vss", "+", "-"]
|
|
208
|
+
nets = [n for n in nets if any(kw in n.name.lower() for kw in power_keywords)]
|
|
209
|
+
|
|
210
|
+
# Sub-sheet detection: unnamed nets can't be resolved without hierarchy
|
|
211
|
+
from .netlist import _find_root_schematic
|
|
212
|
+
|
|
213
|
+
sch_path = Path(file_path)
|
|
214
|
+
root_sch = _find_root_schematic(sch_path)
|
|
215
|
+
subsheet_note = ""
|
|
216
|
+
if root_sch:
|
|
217
|
+
subsheet_note = (
|
|
218
|
+
f"**Note:** `{sch_path.name}` is a sub-sheet. "
|
|
219
|
+
f"Unnamed wire-only nets cannot be resolved without the full "
|
|
220
|
+
f"hierarchy context and will not appear here. "
|
|
221
|
+
f"Use `generate_netlist` on the root schematic `{root_sch.name}` "
|
|
222
|
+
f"for complete net data."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if not nets:
|
|
226
|
+
if subsheet_note:
|
|
227
|
+
return f"No nets found.\n\n{subsheet_note}"
|
|
228
|
+
return "No nets found."
|
|
229
|
+
|
|
230
|
+
# Format output
|
|
231
|
+
lines = [
|
|
232
|
+
f"# Nets in {file_path}",
|
|
233
|
+
f"Total: {len(nets)} net(s)",
|
|
234
|
+
"",
|
|
235
|
+
"| Net Name | Type | Code |",
|
|
236
|
+
"|----------|------|------|",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
for net in sorted(nets, key=lambda n: n.name):
|
|
240
|
+
lines.append(f"| {net.name} | {net.type} | {net.code} |")
|
|
241
|
+
|
|
242
|
+
if subsheet_note:
|
|
243
|
+
lines.append("")
|
|
244
|
+
lines.append(subsheet_note)
|
|
245
|
+
|
|
246
|
+
return "\n".join(lines)
|
|
247
|
+
|
|
248
|
+
except FileNotFoundError as e:
|
|
249
|
+
return f"Error: {e}"
|
|
250
|
+
except Exception as e:
|
|
251
|
+
return f"Error parsing schematic: {e}"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@mcp.tool()
|
|
255
|
+
async def get_schematic_info(file_path: str) -> str:
|
|
256
|
+
"""Get general information about a schematic file.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
file_path: Path to .kicad_sch file
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Schematic metadata and statistics
|
|
263
|
+
"""
|
|
264
|
+
try:
|
|
265
|
+
parser = SchematicParser(file_path)
|
|
266
|
+
title_block = parser.get_title_block()
|
|
267
|
+
components = parser.get_components()
|
|
268
|
+
nets = parser.get_nets()
|
|
269
|
+
sheets = parser.get_sheets()
|
|
270
|
+
|
|
271
|
+
# Count components by type
|
|
272
|
+
component_counts = {}
|
|
273
|
+
for comp in components:
|
|
274
|
+
prefix = "".join(c for c in comp.reference if c.isalpha())
|
|
275
|
+
component_counts[prefix] = component_counts.get(prefix, 0) + 1
|
|
276
|
+
|
|
277
|
+
# Format output
|
|
278
|
+
lines = [
|
|
279
|
+
f"# Schematic Information: {file_path}",
|
|
280
|
+
"",
|
|
281
|
+
"## Project Information",
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
if title_block.get("title"):
|
|
285
|
+
lines.append(f"**Title:** {title_block['title']}")
|
|
286
|
+
if title_block.get("company"):
|
|
287
|
+
lines.append(f"**Company:** {title_block['company']}")
|
|
288
|
+
if title_block.get("date"):
|
|
289
|
+
lines.append(f"**Date:** {title_block['date']}")
|
|
290
|
+
if title_block.get("rev"):
|
|
291
|
+
lines.append(f"**Revision:** {title_block['rev']}")
|
|
292
|
+
|
|
293
|
+
lines.extend([
|
|
294
|
+
"",
|
|
295
|
+
"## Statistics",
|
|
296
|
+
f"**Total Components:** {len(components)}",
|
|
297
|
+
f"**Total Nets:** {len(nets)}",
|
|
298
|
+
f"**Hierarchical Sheets:** {len(sheets)}",
|
|
299
|
+
"",
|
|
300
|
+
"## Components by Type",
|
|
301
|
+
])
|
|
302
|
+
|
|
303
|
+
for prefix in sorted(component_counts.keys()):
|
|
304
|
+
lines.append(f"- {prefix}: {component_counts[prefix]}")
|
|
305
|
+
|
|
306
|
+
if sheets:
|
|
307
|
+
lines.extend([
|
|
308
|
+
"",
|
|
309
|
+
"## Hierarchical Sheets",
|
|
310
|
+
])
|
|
311
|
+
for sheet in sheets:
|
|
312
|
+
lines.append(f"- {sheet['name']}: {sheet['file']}")
|
|
313
|
+
|
|
314
|
+
return "\n".join(lines)
|
|
315
|
+
|
|
316
|
+
except FileNotFoundError as e:
|
|
317
|
+
return f"Error: {e}"
|
|
318
|
+
except Exception as e:
|
|
319
|
+
return f"Error parsing schematic: {e}"
|