kcaa 0.2.2__tar.gz → 0.2.4__tar.gz

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 (94) hide show
  1. {kcaa-0.2.2 → kcaa-0.2.4}/.gitignore +1 -0
  2. {kcaa-0.2.2 → kcaa-0.2.4}/PKG-INFO +8 -8
  3. {kcaa-0.2.2 → kcaa-0.2.4}/README.md +7 -7
  4. {kcaa-0.2.2 → kcaa-0.2.4}/README_CN.md +7 -7
  5. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/server.py +14 -8
  6. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/export_tools.py +0 -9
  7. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/netlist_tools.py +0 -60
  8. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pattern_tools.py +0 -49
  9. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pcb_edit_tools.py +99 -26
  10. kcaa-0.2.4/kcaa/tools/pcb_library_tools.py +907 -0
  11. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pcb_placement_tools.py +139 -72
  12. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pcb_query_tools.py +152 -93
  13. kcaa-0.2.4/kcaa/tools/project_tools.py +132 -0
  14. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/sheet_tools.py +33 -17
  15. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/symbol_edit_tools.py +159 -88
  16. kcaa-0.2.4/kcaa/tools/version_tools.py +139 -0
  17. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/wire_edit_tools.py +79 -2
  18. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/footprint_database.py +198 -59
  19. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/footprint_index_manager.py +127 -23
  20. kcaa-0.2.4/kcaa/utils/fp_lib_table_utils.py +137 -0
  21. kcaa-0.2.4/kcaa/utils/pcb_footprint_utils.py +639 -0
  22. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/pcb_library_utils.py +1 -0
  23. kcaa-0.2.4/kcaa/utils/version_manager.py +250 -0
  24. {kcaa-0.2.2 → kcaa-0.2.4}/kicad_plugin/README.md +3 -3
  25. {kcaa-0.2.2 → kcaa-0.2.4}/pyproject.toml +1 -1
  26. kcaa-0.2.2/kcaa/tools/pcb_library_tools.py +0 -422
  27. kcaa-0.2.2/kcaa/tools/project_tools.py +0 -62
  28. kcaa-0.2.2/kcaa/tools/version_tools.py +0 -134
  29. kcaa-0.2.2/kcaa/utils/pcb_footprint_utils.py +0 -240
  30. kcaa-0.2.2/kcaa/utils/version_manager.py +0 -143
  31. {kcaa-0.2.2 → kcaa-0.2.4}/LICENSE +0 -0
  32. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/__init__.py +0 -0
  33. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/context.py +0 -0
  34. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/prompts/__init__.py +0 -0
  35. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/prompts/bom_prompts.py +0 -0
  36. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/prompts/drc_prompt.py +0 -0
  37. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/prompts/pattern_prompts.py +0 -0
  38. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/prompts/templates.py +0 -0
  39. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/resources/__init__.py +0 -0
  40. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/resources/bom_resources.py +0 -0
  41. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/resources/drc_resources.py +0 -0
  42. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/resources/files.py +0 -0
  43. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/resources/netlist_resources.py +0 -0
  44. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/resources/pattern_resources.py +0 -0
  45. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/resources/projects.py +0 -0
  46. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/__init__.py +0 -0
  47. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/a_star.py +0 -0
  48. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/grid_a_star.py +0 -0
  49. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/path_postprocess.py +0 -0
  50. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/router.py +0 -0
  51. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/via_check.py +0 -0
  52. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/visibility_graph.py +0 -0
  53. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/router/world_model.py +0 -0
  54. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/__init__.py +0 -0
  55. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/analysis_tools.py +0 -0
  56. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/bom_tools.py +0 -0
  57. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/drc_tools.py +0 -0
  58. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/kipy_tools.py +0 -0
  59. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pcb_group_tools.py +0 -0
  60. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pcb_placement_helpers.py +0 -0
  61. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pcb_routing_tools.py +0 -0
  62. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/pcb_zone_tools.py +0 -0
  63. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/placement_helpers.py +0 -0
  64. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/schematic_group_tools.py +0 -0
  65. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/skill_tools.py +0 -0
  66. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/symbol_tools.py +0 -0
  67. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/tools/validation_tools.py +0 -0
  68. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/__init__.py +0 -0
  69. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/boundary_validator.py +0 -0
  70. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/component_utils.py +0 -0
  71. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/config.py +0 -0
  72. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/file_utils.py +0 -0
  73. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/ipc_drc.py +0 -0
  74. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/kicad_api_detection.py +0 -0
  75. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/kicad_cli.py +0 -0
  76. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/kicad_utils.py +0 -0
  77. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/kipy_reload.py +0 -0
  78. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/net_settings.py +0 -0
  79. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/netlist_parser.py +0 -0
  80. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/path_validator.py +0 -0
  81. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/pattern_recognition.py +0 -0
  82. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/pcb_board_utils.py +0 -0
  83. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/pcb_design_rules.py +0 -0
  84. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/pcb_sexp_utils.py +0 -0
  85. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/schematic_sexp_utils.py +0 -0
  86. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/secure_subprocess.py +0 -0
  87. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/skip_compat.py +0 -0
  88. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/skip_helpers.py +0 -0
  89. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/symbol_database.py +0 -0
  90. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/symbol_extractor.py +0 -0
  91. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/symbol_geometry.py +0 -0
  92. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/symbol_index_manager.py +0 -0
  93. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/symbol_index_reader.py +0 -0
  94. {kcaa-0.2.2 → kcaa-0.2.4}/kcaa/utils/temp_dir_manager.py +0 -0
@@ -73,6 +73,7 @@ fp-info-cache
73
73
  *.kicad_pcb-bak
74
74
  *.kicad_sch-bak
75
75
  *.kicad_pro-bak
76
+ **/.versions/
76
77
  *.kicad_prl
77
78
  *.kicad_prl-bak
78
79
  *.kicad_sch.lck
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kcaa
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Model Context Protocol (MCP) server for KiCad electronic design automation (EDA) files
5
5
  Project-URL: Homepage, https://github.com/paul356/KiCad-AI-Assistant
6
6
  Project-URL: Bug Tracker, https://github.com/paul356/KiCad-AI-Assistant/issues
@@ -263,10 +263,8 @@ kcaa
263
263
  | Tool | Description |
264
264
  |------|-------------|
265
265
  | `extract_schematic_netlist` | Extract netlist from a schematic |
266
- | `extract_project_netlist` | Extract netlist for a whole project |
267
266
  | `find_component_connections` | Find all connections for a component |
268
267
  | `identify_circuit_patterns` | Identify common circuit patterns |
269
- | `analyze_project_circuit_patterns` | Analyze circuit patterns in a project |
270
268
  | `validate_project` | Basic validation of a KiCad project |
271
269
  | `validate_project_boundaries` | Validate component boundaries |
272
270
  | `generate_validation_report` | Generate a comprehensive validation report |
@@ -292,6 +290,9 @@ kcaa
292
290
  | `list_footprint_libraries` | List all available footprint libraries |
293
291
  | `search_footprints` | Search footprints by name, description, or tag |
294
292
  | `get_footprint_details` | Get footprint details (pads, bounding box, etc.) |
293
+ | `find_footprints_not_in_libraries` | List board footprints missing from every indexed library |
294
+ | `create_footprint_library` | Create a global 3rdparty or project-local footprint library |
295
+ | `add_footprints_to_library` | Export explicitly named board footprints into a library |
295
296
 
296
297
  ### PCB Query
297
298
 
@@ -323,7 +324,7 @@ kcaa
323
324
 
324
325
  | Tool | Description |
325
326
  |------|-------------|
326
- | `set_footprint_position` | Move and/or rotate a single footprint |
327
+ | `set_footprint_position` | Move and/or rotate one or more footprints (via `items`) |
327
328
  | `flip_footprint` | Flip a footprint between top and bottom layer |
328
329
  | `align_footprints` | Align footprints to the same axis |
329
330
  | `distribute_footprints` | Distribute footprints evenly along an axis |
@@ -371,11 +372,10 @@ kcaa
371
372
 
372
373
  | Tool | Description |
373
374
  |------|-------------|
374
- | `save_file_version` | Save a version snapshot for rollback |
375
- | `list_file_versions` | List saved version snapshots |
376
- | `restore_file_version` | Restore to a previously saved version |
375
+ | `save_project_version` | Save a project version archive (schematic+PCB+project file) |
376
+ | `list_project_versions` | List saved project version archives |
377
+ | `restore_project_version` | Restore project files together to a saved archive |
377
378
  | `generate_pcb_thumbnail` | Render a PCB thumbnail image |
378
- | `generate_project_thumbnail` | Render a project thumbnail |
379
379
 
380
380
  ### Skill System
381
381
 
@@ -224,10 +224,8 @@ kcaa
224
224
  | Tool | Description |
225
225
  |------|-------------|
226
226
  | `extract_schematic_netlist` | Extract netlist from a schematic |
227
- | `extract_project_netlist` | Extract netlist for a whole project |
228
227
  | `find_component_connections` | Find all connections for a component |
229
228
  | `identify_circuit_patterns` | Identify common circuit patterns |
230
- | `analyze_project_circuit_patterns` | Analyze circuit patterns in a project |
231
229
  | `validate_project` | Basic validation of a KiCad project |
232
230
  | `validate_project_boundaries` | Validate component boundaries |
233
231
  | `generate_validation_report` | Generate a comprehensive validation report |
@@ -253,6 +251,9 @@ kcaa
253
251
  | `list_footprint_libraries` | List all available footprint libraries |
254
252
  | `search_footprints` | Search footprints by name, description, or tag |
255
253
  | `get_footprint_details` | Get footprint details (pads, bounding box, etc.) |
254
+ | `find_footprints_not_in_libraries` | List board footprints missing from every indexed library |
255
+ | `create_footprint_library` | Create a global 3rdparty or project-local footprint library |
256
+ | `add_footprints_to_library` | Export explicitly named board footprints into a library |
256
257
 
257
258
  ### PCB Query
258
259
 
@@ -284,7 +285,7 @@ kcaa
284
285
 
285
286
  | Tool | Description |
286
287
  |------|-------------|
287
- | `set_footprint_position` | Move and/or rotate a single footprint |
288
+ | `set_footprint_position` | Move and/or rotate one or more footprints (via `items`) |
288
289
  | `flip_footprint` | Flip a footprint between top and bottom layer |
289
290
  | `align_footprints` | Align footprints to the same axis |
290
291
  | `distribute_footprints` | Distribute footprints evenly along an axis |
@@ -332,11 +333,10 @@ kcaa
332
333
 
333
334
  | Tool | Description |
334
335
  |------|-------------|
335
- | `save_file_version` | Save a version snapshot for rollback |
336
- | `list_file_versions` | List saved version snapshots |
337
- | `restore_file_version` | Restore to a previously saved version |
336
+ | `save_project_version` | Save a project version archive (schematic+PCB+project file) |
337
+ | `list_project_versions` | List saved project version archives |
338
+ | `restore_project_version` | Restore project files together to a saved archive |
338
339
  | `generate_pcb_thumbnail` | Render a PCB thumbnail image |
339
- | `generate_project_thumbnail` | Render a project thumbnail |
340
340
 
341
341
  ### Skill System
342
342
 
@@ -223,10 +223,8 @@ kcaa
223
223
  | 工具 | 说明 |
224
224
  |------|------|
225
225
  | `extract_schematic_netlist` | 从原理图提取网表 |
226
- | `extract_project_netlist` | 提取整个项目的网表 |
227
226
  | `find_component_connections` | 查找元件的所有连接 |
228
227
  | `identify_circuit_patterns` | 识别常见电路模式 |
229
- | `analyze_project_circuit_patterns` | 分析项目中的电路模式 |
230
228
  | `validate_project` | 项目基本验证 |
231
229
  | `validate_project_boundaries` | 验证元件边界 |
232
230
  | `generate_validation_report` | 生成综合验证报告 |
@@ -252,6 +250,9 @@ kcaa
252
250
  | `list_footprint_libraries` | 列出所有可用封装库 |
253
251
  | `search_footprints` | 按名称/描述/标签搜索封装 |
254
252
  | `get_footprint_details` | 获取封装详情(焊盘、边界框等) |
253
+ | `find_footprints_not_in_libraries` | 列出板上缺失于所有已索引库的封装 |
254
+ | `create_footprint_library` | 创建全局 3rdparty 或项目级封装库 |
255
+ | `add_footprints_to_library` | 将显式指定的板载封装导出到库 |
255
256
 
256
257
  ### PCB 查询
257
258
 
@@ -283,7 +284,7 @@ kcaa
283
284
 
284
285
  | 工具 | 说明 |
285
286
  |------|------|
286
- | `set_footprint_position` | 移动/旋转单个封装 |
287
+ | `set_footprint_position` | 批量移动/旋转一个或多个封装 |
287
288
  | `flip_footprint` | 将封装在顶层/底层之间翻转 |
288
289
  | `align_footprints` | 将封装对齐到同一坐标轴 |
289
290
  | `distribute_footprints` | 沿坐标轴等间距分布封装 |
@@ -331,11 +332,10 @@ kcaa
331
332
 
332
333
  | 工具 | 说明 |
333
334
  |------|------|
334
- | `save_file_version` | 保存版本快照以便回退 |
335
- | `list_file_versions` | 列出已保存的版本快照 |
336
- | `restore_file_version` | 恢复到之前保存的版本 |
335
+ | `save_project_version` | 保存项目版本归档(原理图+PCB+项目文件) |
336
+ | `list_project_versions` | 列出已保存的项目版本归档 |
337
+ | `restore_project_version` | 将项目文件一起恢复到已保存的归档 |
337
338
  | `generate_pcb_thumbnail` | 渲染 PCB 缩略图 |
338
- | `generate_project_thumbnail` | 渲染项目缩略图 |
339
339
 
340
340
  ### 技能系统
341
341
 
@@ -6,11 +6,12 @@ Two server profiles are supported, selected via the KICAD_MCP_PROFILE environmen
6
6
  full (default) — registers all tools, resources, and prompts. Intended for
7
7
  standalone MCP clients such as Claude Desktop.
8
8
 
9
- plugin — registers only the skip-based schematic editing tools
10
- (netlist, symbol, component, wire/junction). No resources,
11
- no prompts, no kicad-cli-dependent tools. Intended for the
12
- KiCad plugin integration where the plugin supplies project
13
- context directly and the LLM drives editing via tool calls.
9
+ plugin — registers the skip-based schematic/PCB editing tools
10
+ plus the ``get_project_structure`` query tool. No
11
+ resources, no prompts, no kicad-cli-dependent tools.
12
+ Intended for the KiCad plugin integration where the
13
+ plugin supplies project context directly and the LLM
14
+ drives editing via tool calls.
14
15
  """
15
16
 
16
17
  import atexit
@@ -40,6 +41,7 @@ from kcaa.tools.pcb_query_tools import register_pcb_query_tools
40
41
  from kcaa.tools.pcb_routing_tools import register_pcb_routing_tools
41
42
  from kcaa.tools.pcb_zone_tools import register_pcb_zone_tools
42
43
  from kcaa.tools.placement_helpers import register_placement_helpers
44
+ from kcaa.tools.project_tools import register_project_tools
43
45
  from kcaa.tools.schematic_group_tools import register_schematic_group_tools
44
46
  from kcaa.tools.sheet_tools import register_sheet_tools
45
47
  from kcaa.tools.skill_tools import register_skill_tools
@@ -110,9 +112,12 @@ def register_signal_handlers(server: FastMCP) -> None:
110
112
 
111
113
 
112
114
  def _register_plugin_profile(mcp: FastMCP) -> None:
113
- """Register only the skip-based schematic/PCB editing tools + IPC-based tools for the plugin profile."""
115
+ """Register the skip-based schematic/PCB editing tools + IPC-based tools for the plugin profile."""
114
116
  logging.info("Registering plugin profile: schematic + PCB tools")
115
117
  register_netlist_tools(mcp)
118
+ # Project structure query for the plugin LLM; keep full-profile
119
+ # management tools (list_projects/open_project) out of this profile.
120
+ register_project_tools(mcp, tools=("get_project_structure",))
116
121
  register_symbol_tools(mcp)
117
122
  register_symbol_edit_tools(mcp)
118
123
  register_sheet_tools(mcp)
@@ -205,8 +210,9 @@ def create_server(profile: ServerProfile = "full") -> FastMCP:
205
210
  Args:
206
211
  profile: Server capability profile.
207
212
  "full" — all tools, resources, and prompts (default; for Claude Desktop etc.)
208
- "plugin" — plugin-facing profile: 26 skip-based schematic editing tools only,
209
- no resources, no prompts, no kicad-cli-dependent tools.
213
+ "plugin" — plugin-facing profile: skip-based schematic/PCB editing
214
+ tools plus the get_project_structure query tool; no
215
+ resources, no prompts, no kicad-cli-dependent tools.
210
216
  Override via KICAD_MCP_PROFILE env var.
211
217
  """
212
218
  # Allow env var to override the profile argument; strip whitespace/case variants
@@ -99,15 +99,6 @@ def register_export_tools(mcp: FastMCP) -> None:
99
99
  await ctx.info(f"Error: {str(e)}")
100
100
  return None
101
101
 
102
- @mcp.tool()
103
- async def generate_project_thumbnail(project_path: str, ctx: Context | None):
104
- """Generate a thumbnail of a KiCad project's PCB layout (Alias for generate_pcb_thumbnail)."""
105
- # This function now just calls the main CLI-based thumbnail generator
106
- print(
107
- f"generate_project_thumbnail called, redirecting to generate_pcb_thumbnail for {project_path}"
108
- )
109
- return await generate_pcb_thumbnail(project_path, ctx)
110
-
111
102
 
112
103
  # Helper functions for thumbnail generation
113
104
  async def generate_thumbnail_with_cli(pcb_file: str, ctx: Context | None):
@@ -19,66 +19,6 @@ def register_netlist_tools(mcp: FastMCP) -> None:
19
19
  mcp: The FastMCP server instance
20
20
  """
21
21
 
22
- @mcp.tool()
23
- async def extract_project_netlist(project_path: str, ctx: Context | None) -> dict[str, Any]:
24
- """Extract netlist from a KiCad project's schematic.
25
-
26
- This tool finds the schematic associated with a KiCad project
27
- and extracts its netlist information.
28
-
29
- Args:
30
- project_path: Path to the KiCad project file (.kicad_pro)
31
- ctx: MCP context for progress reporting
32
-
33
- Returns:
34
- Dictionary with netlist information
35
- """
36
- print(f"Extracting netlist for project: {project_path}")
37
-
38
- if not os.path.exists(project_path):
39
- print(f"Project not found: {project_path}")
40
- if ctx:
41
- ctx.info(f"Project not found: {project_path}")
42
- return {"success": False, "error": f"Project not found: {project_path}"}
43
-
44
- # Report progress
45
- if ctx:
46
- await ctx.report_progress(10, 100)
47
-
48
- # Get the schematic file
49
- try:
50
- files = get_project_files(project_path)
51
-
52
- if "schematic" not in files:
53
- print("Schematic file not found in project")
54
- if ctx:
55
- ctx.info("Schematic file not found in project")
56
- return {"success": False, "error": "Schematic file not found in project"}
57
-
58
- schematic_path = files["schematic"]
59
- print(f"Found schematic file: {schematic_path}")
60
- if ctx:
61
- ctx.info(f"Found schematic file: {os.path.basename(schematic_path)}")
62
-
63
- # Extract netlist
64
- if ctx:
65
- await ctx.report_progress(20, 100)
66
-
67
- # Call the schematic netlist extraction
68
- result = await extract_schematic_netlist(schematic_path, ctx=ctx)
69
-
70
- # Add project path to result
71
- if "success" in result and result["success"]:
72
- result["project_path"] = project_path
73
-
74
- return result
75
-
76
- except Exception as e:
77
- print(f"Error extracting project netlist: {str(e)}")
78
- if ctx:
79
- ctx.info(f"Error extracting project netlist: {str(e)}")
80
- return {"success": False, "error": str(e)}
81
-
82
22
  @mcp.tool()
83
23
  async def extract_schematic_netlist(
84
24
  schematic_path: str,
@@ -7,7 +7,6 @@ from typing import Any
7
7
 
8
8
  from fastmcp import Context, FastMCP
9
9
 
10
- from kcaa.utils.file_utils import get_project_files
11
10
  from kcaa.utils.netlist_parser import extract_netlist
12
11
  from kcaa.utils.pattern_recognition import (
13
12
  identify_amplifiers,
@@ -155,51 +154,3 @@ def register_pattern_tools(mcp: FastMCP) -> None:
155
154
  if ctx:
156
155
  ctx.info(f"Error identifying circuit patterns: {str(e)}")
157
156
  return {"success": False, "error": str(e)}
158
-
159
- @mcp.tool()
160
- async def analyze_project_circuit_patterns(
161
- project_path: str, ctx: Context | None
162
- ) -> dict[str, Any]:
163
- """Identify circuit patterns in a KiCad project's schematic.
164
-
165
- Args:
166
- project_path: Path to the KiCad project file (.kicad_pro)
167
- ctx: MCP context for progress reporting
168
-
169
- Returns:
170
- Dictionary with identified circuit patterns
171
- """
172
- if not os.path.exists(project_path):
173
- if ctx:
174
- ctx.info(f"Project not found: {project_path}")
175
- return {"success": False, "error": f"Project not found: {project_path}"}
176
-
177
- # Report progress
178
- if ctx:
179
- await ctx.report_progress(10, 100)
180
-
181
- # Get the schematic file
182
- try:
183
- files = get_project_files(project_path)
184
-
185
- if "schematic" not in files:
186
- if ctx:
187
- ctx.info("Schematic file not found in project")
188
- return {"success": False, "error": "Schematic file not found in project"}
189
-
190
- schematic_path = files["schematic"]
191
- if ctx:
192
- ctx.info(f"Found schematic file: {os.path.basename(schematic_path)}")
193
-
194
- # Identify patterns in the schematic
195
- result = await identify_circuit_patterns(schematic_path, ctx)
196
-
197
- # Add project path to result
198
- if "success" in result and result["success"]:
199
- result["project_path"] = project_path
200
-
201
- return result
202
-
203
- except Exception as e:
204
- ctx.info(f"Error analyzing project circuit patterns: {str(e)}")
205
- return {"success": False, "error": str(e)}
@@ -309,47 +309,120 @@ def register_pcb_edit_tools(mcp: FastMCP) -> None:
309
309
  @mcp.tool()
310
310
  async def set_footprint_property(
311
311
  pcb_path: str,
312
- reference: str,
313
- property_name: str,
314
- value: str,
312
+ items: list[dict[str, Any]],
315
313
  ctx: Context | None,
316
- ) -> dict[str, str]:
317
- """Update a property of a footprint on the PCB board.
314
+ ) -> dict[str, Any]:
315
+ """Update a property on footprints of the PCB board.
318
316
 
319
317
  Common property names: ``Reference``, ``Value``, ``Datasheet``,
320
318
  ``Description``. Custom user fields are also supported.
321
319
 
320
+ Each item in *items* carries its own property and value
321
+ (``{"reference": ..., "property_name": ..., "value": ...}``).
322
+ All items are processed in one parse + one save (single ``.bak``).
323
+ Partial-apply: a footprint that cannot be found or lacks the
324
+ property keeps its own error in ``results`` while the remaining
325
+ footprints are still applied and saved. Empty, duplicate, or
326
+ malformed items are rejected up front.
327
+
322
328
  A .kicad_pcb.bak backup is created before writing.
329
+
330
+ Args:
331
+ pcb_path: Absolute path to the .kicad_pcb file.
332
+ items: List of per-footprint specs, e.g. ``[{"reference":
333
+ "R1", "property_name": "Value", "value": "22k"}]``.
334
+ Keys: ``reference`` (str, required, unique),
335
+ ``property_name`` (str, required),
336
+ ``value`` (str, required).
337
+
338
+ Returns:
339
+ dict with: results (per-footprint {reference, property_name,
340
+ previous_value, new_value} or {reference, error}),
341
+ success (all applied), count, applied_count, failure_count,
342
+ backup_path, pcb_path.
323
343
  """
344
+ _ITEM_KEYS = {"reference", "property_name", "value"}
345
+ if not items:
346
+ return {"error": "items must not be empty"}
347
+ for item in items:
348
+ if not isinstance(item, dict):
349
+ return {"error": "each item must be a dict with reference, property_name, value"}
350
+ unknown = set(item) - _ITEM_KEYS
351
+ if unknown:
352
+ return {
353
+ "error": f"Unknown item fields: {sorted(unknown)}. Valid: {sorted(_ITEM_KEYS)}"
354
+ }
355
+ ref = item.get("reference")
356
+ if not isinstance(ref, str) or not ref:
357
+ return {"error": "items must not contain empty or missing references"}
358
+ if not isinstance(item.get("property_name"), str) or not item["property_name"]:
359
+ return {"error": "items must contain a non-empty property_name"}
360
+ if not isinstance(item.get("value"), str):
361
+ return {"error": "items must contain a string value"}
362
+ refs = [item["reference"] for item in items]
363
+ if len(set(refs)) != len(refs):
364
+ return {"error": "items must not contain duplicate references"}
365
+
324
366
  data = load_pcb(pcb_path)
325
- try:
326
- fp = find_footprint(data, reference)
327
- except KeyError as exc:
328
- return {"error": str(exc)}
329
367
 
330
- old_value = get_fp_property(fp, property_name)
331
- if old_value is None:
368
+ def apply_one(item: dict[str, Any]) -> dict[str, Any]:
369
+ ref = item["reference"]
370
+ name = item["property_name"]
371
+ val = item["value"]
372
+ try:
373
+ fp = find_footprint(data, ref)
374
+ except KeyError as exc:
375
+ return {"error": str(exc), "reference": ref}
376
+
377
+ old_value = get_fp_property(fp, name)
378
+ if old_value is None:
379
+ return {
380
+ "error": (
381
+ f"Property '{name}' not found on footprint '{ref}'. "
382
+ f"Use get_footprint to see available properties."
383
+ ),
384
+ "reference": ref,
385
+ }
386
+
387
+ updated = set_fp_property(fp, name, val)
388
+ if not updated:
389
+ return {
390
+ "error": f"Failed to update property '{name}' on '{ref}'.",
391
+ "reference": ref,
392
+ }
393
+
332
394
  return {
333
- "error": (
334
- f"Property '{property_name}' not found on footprint '{reference}'. "
335
- f"Use get_footprint to see available properties."
336
- )
395
+ "success": True,
396
+ "reference": ref,
397
+ "property_name": name,
398
+ "previous_value": old_value,
399
+ "new_value": val,
337
400
  }
338
401
 
339
- updated = set_fp_property(fp, property_name, value)
340
- if not updated:
341
- return {"error": f"Failed to update property '{property_name}' on '{reference}'."}
402
+ results: list[dict[str, Any]] = []
403
+ for item in items:
404
+ ref = item["reference"]
405
+ try:
406
+ results.append(apply_one(item))
407
+ except Exception as exc:
408
+ log.warning("set_footprint_property: reference %r failed: %s", ref, exc)
409
+ results.append({"error": f"{ref}: {exc}", "reference": ref})
342
410
 
343
- try:
344
- backup_path = save_pcb(pcb_path, data)
345
- except OSError as exc:
346
- return {"error": f"Failed to write PCB file: {exc}"}
411
+ applied_count = sum(1 for r in results if r.get("success"))
412
+
413
+ backup_path: str | None = None
414
+ if applied_count > 0:
415
+ try:
416
+ backup_path = save_pcb(pcb_path, data)
417
+ except OSError as exc:
418
+ return {"error": f"Failed to write PCB file: {exc}"}
347
419
 
348
420
  return {
349
- "reference": reference,
350
- "property_name": property_name,
351
- "previous_value": old_value,
352
- "new_value": value,
421
+ "success": applied_count == len(results) and len(results) > 0,
422
+ "results": results,
423
+ "count": len(results),
424
+ "applied_count": applied_count,
425
+ "failure_count": len(results) - applied_count,
353
426
  "backup_path": backup_path,
354
427
  "pcb_path": pcb_path,
355
428
  }