@huaqiu/dsh-tool-kicad-schematic-gen 0.6.9 → 0.6.10
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 +6 -2
- package/README.zh.md +11 -7
- package/lib/client.js +4 -0
- package/lib/index.js +101 -74
- package/package.json +3 -3
- package/skills/kicad-schematic-design/SKILL.md +14 -4
- package/skills/kicad-schematic-design/assets/drawing-patterns/LICENSE.kicad-symbols.md +27 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/crystal-load.mjs +35 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/crystal-load.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/dcdc-boost.mjs +46 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/dcdc-boost.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/dcdc-buck.mjs +44 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/dcdc-buck.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/dcdc-geometry.mjs +23 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/decoupling-bank.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/generate-supplement.mjs +80 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/generate-systems.mjs +71 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/generate.mjs +10 -2
- package/skills/kicad-schematic-design/assets/drawing-patterns/geometry.mjs +1 -1
- package/skills/kicad-schematic-design/assets/drawing-patterns/i2c-pullups.mjs +23 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/i2c-pullups.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/ldo-fixed-on.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/ldo-three-pin.mjs +49 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/ldo-three-pin.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/led-power-indicator.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/mcu-external-crystal.mjs +6 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/mcu-external-crystal.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/mcu-minimum.mjs +90 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/mcu-minimum.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/mosfet-low-side.mjs +44 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/mosfet-low-side.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/pullup-button.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/reference-symbols.kicad_sym +3790 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/reference-symbols.md +34 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/render.mjs +7 -1
- package/skills/kicad-schematic-design/assets/drawing-patterns/reset-rc.mjs +27 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/reset-rc.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/swd-header.mjs +24 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/swd-header.png +0 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/usb-c-device.mjs +35 -0
- package/skills/kicad-schematic-design/assets/drawing-patterns/usb-c-device.png +0 -0
- package/skills/kicad-schematic-design/references/component-pcb.md +6 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/crystal-load.md +19 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/dcdc-boost.md +15 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/dcdc-buck.md +15 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/i2c-pullups.md +19 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/ldo-three-pin.md +19 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/mcu-external-crystal.md +11 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/mcu-minimum.md +17 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/mosfet-low-side.md +17 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/reset-enable.md +1 -1
- package/skills/kicad-schematic-design/references/drawing-patterns/reset-rc.md +19 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/swd-header.md +15 -0
- package/skills/kicad-schematic-design/references/drawing-patterns/usb-c-device.md +17 -0
- package/skills/kicad-schematic-design/references/drawing-patterns.md +20 -1
- package/skills/kicad-schematic-design/references/layout-delivery.md +1 -1
- package/skills/kicad-schematic-design/references/troubleshooting.md +22 -0
- package/src/client/overview-result.ts +1 -0
- package/src/components/identity.ts +3 -5
- package/src/components/import-component.ts +3 -5
- package/src/components/import-symbol.ts +2 -4
- package/src/components/models.ts +3 -15
- package/src/components/project-footprint.ts +4 -6
- package/src/schematic/overview.ts +6 -15
- package/src/schematic/tools.ts +37 -16
- package/src/skills.ts +17 -1
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Imports remain workspace scoped and use DSH sandbox policy. Model inspection rep
|
|
|
12
12
|
|
|
13
13
|
Build, typecheck and test from the repository workspace. Restart the KiCad assistant after replacing the old bundles. See [merge notes](../../docs/SCHEMATIC-MERGE.md).
|
|
14
14
|
|
|
15
|
-
Paired import: `import_kicad_component({manufacturer_id, mpn, projectDir})` downloads a symbol and footprint, registers `sym-lib-table` and `fp-lib-table`, and returns `footprintId`. Place with `addSymbol({libraryPath, libId, footprint: footprintId, ...})`. Success requires both usable models and matching unique pin/pad number sets. Public tool arguments use required `manufacturer_id` and `mpn`, matching `@huaqiu/dsh-tool-part-search`; imports also accept optional `projectDir` (defaults to the workspace root). `mfgId` is
|
|
15
|
+
Paired import: `import_kicad_component({manufacturer_id, mpn, projectDir})` downloads a symbol and footprint, registers `sym-lib-table` and `fp-lib-table`, and returns `footprintId`. Place with `addSymbol({libraryPath, libId, footprint: footprintId, ...})`. Success requires both usable models and matching unique pin/pad number sets. Public tool arguments use required `manufacturer_id` and `mpn`, matching `@huaqiu/dsh-tool-part-search`; imports also accept optional `projectDir` (defaults to the workspace root). The obsolete `mfgId` parameter is rejected; all callers must use `manufacturer_id`. See the skill's [component and PCB reference](skills/kicad-schematic-design/references/component-pcb.md).
|
|
16
16
|
|
|
17
17
|
Power symbols: declare actual shared nets with the builder's `requiredPowerSymbols` option. `powerLabelCatalog(nets)` returns current-build label UUIDs, coordinates and imported-module names for reproducible placement. `checkPowerSymbols()` and render/save reject missing symbols or unconverted labels for declared nets. Layout warnings still require visual review. See [power-symbol delivery](skills/kicad-schematic-design/references/power-symbols.md).
|
|
18
18
|
|
|
@@ -20,10 +20,14 @@ Power symbols: declare actual shared nets with the builder's `requiredPowerSymbo
|
|
|
20
20
|
|
|
21
21
|
Standalone common-only symbols with pins (e.g. SW-PTB-2_0_1) are automatically normalized in project copies to unit 1 without changing pins or graphics. Model summaries and import results report builderCompatibility.normalization=common_only_to_unit_1. Inherited, empty, malformed or alternate-only definitions are not normalized. Unsupported imports report part, symbol, sections and source URL.
|
|
22
22
|
|
|
23
|
-
最终概览卡片:正式预览传入 `delivery: true` 和包含全部本地库的 `projectDir`,待本轮正常完成后,在最终回复后的轮次尾部显示页面切换、缩放、ZIP 下载;工具执行期间不展示正式卡片。正式概览必须是本轮最后一个工具操作,之后调用其他工具或普通预览时须重新正式交付;失败、取消、中断的轮次不显示正式卡片。卡片从所属轮次的工具记录恢复,切换会话无需全局缓存。复用已有 PNG,不重新生成电路,无重新生成/详情按钮。需 KiCad Harness 的 webServer、conversation.chat.turnTail 和 useChat(ChatSnapshot 的 locations/nodes
|
|
23
|
+
最终概览卡片:正式预览传入 `delivery: true` 和包含全部本地库的 `projectDir`,待本轮正常完成后,在最终回复后的轮次尾部显示页面切换、缩放、ZIP 下载;工具执行期间不展示正式卡片。正式概览必须是本轮最后一个工具操作,之后调用其他工具或普通预览时须重新正式交付;失败、取消、中断的轮次不显示正式卡片。卡片从所属轮次的工具记录恢复,切换会话无需全局缓存。复用已有 PNG,不重新生成电路,无重新生成/详情按钮。需 KiCad Harness 的 webServer、conversation.chat.turnTail 和 useChat(ChatSnapshot 的 locations/nodes)支持;不可用时仍返回本地预览路径。持久快照默认保存在当前用户家目录的 `.hq-edge/plugin-data/dsh-tool-kicad-schematic-gen/overview-cache/`,家目录通过 `os.homedir()` 动态获取;可用绝对路径 `HQ_EDGE_HOME` 覆盖 `.hq-edge` 根目录,不随 `DSH_HOME` 或插件版本变化。重启后可读取,手动删除快照后对应历史卡片下载将失效。工程、中间产物和预览保存在当前工作区;插件安装目录仅用于读取代码和模板,不读写概览快照。ZIP 为工程目录内相关设计文件与库的快照,不收集脚本、预览或外部库。
|
|
24
24
|
|
|
25
25
|
Module analysis queries use `analyze_kicad_module({schematicPath, view, reference?, pin?, net?, offset?, kicadCliPath?})`, with explicit `view`. Downloads already return a summary: query only needed components, grouped nets or grouped BOM. CLI/project options are resolved internally; optional absolute `kicadCliPath` is for recovery using the existing `get_eda_host_info` result. On `CLI_START_FAILED`, retry analysis with that path and `view:summary`, without downloading again. Successful paths are reused; default CLI command names preserve automatic discovery. Probe errors return `error:{code,executable}` and retain causes; capability failures are distinct from startup failures. Matching same-stem projects and downloaded `.source.kicad_pro` files are discovered. Detail output contains `status`, `items`, and optional `nextOffset`/`warnings`; missing `nextOffset` means completion. Component custom fields require a reference filter. Partial results are reused for detail queries; after fixing configuration use `view:summary` to retry. Full local artifacts are preserved. This replaces the previous analysis query arguments and response contract; existing callers must migrate. See [module analysis](skills/kicad-schematic-design/references/module-analysis.md).
|
|
26
26
|
|
|
27
27
|
方案先行:先保存硬件方案,用 `scripts/run-design.mjs --read-plan <方案.md>` 读回,再按文档编写脚本。运行时必须传 `--plan <path> --plan-sha256 <sha256>`;构建库直接调用也要求 `designPlan:{path,sha256}`。平铺 create 与层次生成工具同样检查方案。缺失、空文档或版本变化时阻止生成;只读模块分析和预览不受影响。构建报告保存方案全文、方案 hash 和脚本 hash。此检查保证文件和版本前置,不代替方案电气完整性及文图一致性审查。
|
|
28
28
|
|
|
29
29
|
电气栅格:默认 50 mil(1.27 mm)。构建器吸附器件放置、导线坐标、标签和模块平移量;`gridPoint/snapGrid` 可用于脚本规划。引脚库必须兼容同一格点,原理图生成与正式交付检查实际引脚及连接点,偏离时阻止交付。已有工程需从脚本重新构建,不通过独立舍入引脚/连线修补。
|
|
30
|
+
|
|
31
|
+
候选预览与交付状态:普通 `render_kicad_module_preview` 可传 `projectDir`,在首版预览时校验全部页的本地封装,无需先复制库到候选目录。`footprintCheck` 返回逐页的实际解析结果,缺库时顶层 `status:error`,已渲染图片仍保留。`renderStatus` 单独表示渲染结果;`deliveryStatus` 为 `not_requested`、`ready` 或 `blocked`。正式交付失败不会返回整体成功;只有 `deliveryStatus:ready` 和有效概览才代表正式快照已创建,这仍不代表电气验证通过。
|
|
32
|
+
|
|
33
|
+
技能加载时提供 `pluginDir`、`skillDir`、`runtimePath`、`runnerPath`、`geometryPath` 等只读资源绝对路径,避免混淆插件根目录与技能根目录。资源安装目录不用于保存项目输出。失败恢复、有限重试及候选检查顺序见技能的 `references/troubleshooting.md`。
|
package/README.zh.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
| 电路模块搜索下载 | eda_module_rag_search、eda_module_category_search、eda_module_precise_search、download_eda_module_schematic |
|
|
9
9
|
| 原理图工具 | get_kicad_module_ports、render_kicad_module_preview、create_kicad_hierarchy、edit_kicad_flat_schematic |
|
|
10
10
|
|
|
11
|
-
同时注册 kicad-schematic-design 技能。元件搜索由KiCad 内置的 @huaqiu/dsh-tool-part-search 提供,调用 search_hqsch_parts。该搜索插件随 KiCad 提供,可在插件管理器中启用;原理图插件本身不注册它的四个查询工具。两者间将搜索结果的 manufacturer.id 映射为 manufacturer_id,mpn 保持不变;公开工具统一要求 manufacturer_id 和 mpn,不再暴露 mfgId;两个导入工具额外支持可选 projectDir
|
|
11
|
+
同时注册 kicad-schematic-design 技能。元件搜索由KiCad 内置的 @huaqiu/dsh-tool-part-search 提供,调用 search_hqsch_parts。该搜索插件随 KiCad 提供,可在插件管理器中启用;原理图插件本身不注册它的四个查询工具。两者间将搜索结果的 manufacturer.id 映射为 manufacturer_id,mpn 保持不变;公开工具统一要求 manufacturer_id 和 mpn,不再暴露 mfgId;两个导入工具额外支持可选 projectDir,默认工作区根目录。内部身份解析同样拒绝旧参数 mfgId,所有调用统一使用 manufacturer_id。
|
|
12
12
|
|
|
13
13
|
配置分为 components、modules.search、modules.download、schematic,使用原配置的默认限额。flatTemplateDir 默认指向本包技能模板。Node 22.19+,KiCad 10 CLI 路径可通过 schematic.kicadCliPath 配置。
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
公开脚本入口是 lib/builder.js 或包的 ./builder 导出。设计技能、资源与模板位于 skills/kicad-schematic-design/。
|
|
18
18
|
|
|
19
|
-
新导入器件库默认保存到 `project/libraries/symbols/` 和 `project/libraries/footprints/`,库表保留在 project 根目录,使用 `${KIPRJMOD}`
|
|
19
|
+
新导入器件库默认保存到 `project/libraries/symbols/` 和 `project/libraries/footprints/`,库表保留在 project 根目录,使用 `${KIPRJMOD}` 相对路径注册。相同器件重复导入复用原文件;不再复用工程根目录中的旧库;同名库表仍指向旧目录时明确报冲突,不自动迁移或覆盖。脚本使用导入返回的实际 libraryPath 和 footprintId;概览 ZIP 会递归收集 libraries 下的器件库。
|
|
20
20
|
|
|
21
21
|
复用模块的封装通过 `localize_kicad_module_footprints` 的 list/import/check 流程本地化。模块下载现在保留 ZIP 中的 `.kicad_mod` 并返回 footprints 列表;缺失时使用明确选择的 manufacturer_id/mpn 查询在线封装。原嵌入符号、引脚和布线不替换,编号集合不匹配时拒绝导入。映射存入 `libraries/mappings/`,在设计脚本中使用 `importModule({...,footprintMappingPath})` 持久应用;来源变化后旧映射会拒绝使用。正式 delivery 在生成概览前验证根图和子页的本地封装引用。映射和封装一并进入项目 ZIP;尺寸、电气功能及 3D 完整性仍需单独确认。已有工程不会在插件加载时自动选择或替换器件。
|
|
22
22
|
|
|
@@ -24,11 +24,11 @@ KiCad CLI 自动从当前运行时安装位置或 PATH 查找,也可通过 KIC
|
|
|
24
24
|
|
|
25
25
|
路径发现由配置与 skill 共用缓存,环境变量变化后重新识别。模型已有 get_eda_host_info 返回结果时,可以把其中的 kicad-cli 绝对路径传给 render_kicad_module_preview 的可选 kicadCliPath;只影响当前调用,不修改默认配置。原理图文件生成和预览不要求 PCB IPC 诊断或 Python 路径查询。
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
正式概览快照默认保存到 `~/.hq-edge/plugin-data/dsh-tool-kicad-schematic-gen/overview-cache/`(`~` 为当前用户家目录,使用 `os.homedir()` 获取);设置绝对路径 `HQ_EDGE_HOME` 时保存到该根目录下同一子目录。不会使用 `DSH_HOME`、`LOCALAPPDATA` 或插件安装目录作为新快照的默认存储位置。快照完整写入后才发布;不再兼容读取插件目录中的旧快照,相关旧会话卡片需重新生成正式概览。不会自动删除历史快照;手动清理对应快照后,旧会话中的下载和放置操作将失效。
|
|
28
28
|
|
|
29
|
-
在工作区根目录执行 pnpm --filter @huaqiu/dsh-tool-kicad-schematic-gen build、typecheck 或 test。使用配套 DSH CLI 注册本目录,替换旧四个插件,并重新打开 KiCad AI
|
|
29
|
+
在工作区根目录执行 pnpm --filter @huaqiu/dsh-tool-kicad-schematic-gen build、typecheck 或 test。使用配套 DSH CLI 注册本目录,替换旧四个插件,并重新打开 KiCad AI 助手。旧脚本须更新为当前接口,不提供历史调用兼容。
|
|
30
30
|
|
|
31
|
-
成套器件导入:`import_kicad_component({manufacturer_id, mpn, projectDir})` 同时下载符号与封装,登记两个项目库表,返回 `footprintId`。放置时传 `footprint: imported.footprintId`。两种实际模型可用且引脚/焊盘编号集合匹配才成功;仍需手册核对尺寸与功能。公开调用只使用 `manufacturer_id
|
|
31
|
+
成套器件导入:`import_kicad_component({manufacturer_id, mpn, projectDir})` 同时下载符号与封装,登记两个项目库表,返回 `footprintId`。放置时传 `footprint: imported.footprintId`。两种实际模型可用且引脚/焊盘编号集合匹配才成功;仍需手册核对尺寸与功能。公开调用只使用 `manufacturer_id`,内部解析层同样拒绝旧 `mfgId` 参数。
|
|
32
32
|
|
|
33
33
|
电源符号:在构建选项中声明实际共享网络 `requiredPowerSymbols`。`powerLabelCatalog(nets)` 返回本次标签 UUID、网络、坐标和导入模块名,按这些条件定位后生成 placements,不引用上一轮 UUID。`checkPowerSymbols()` 与 render/save 检查声明网络的标准符号和残留标签;未完成时拒绝保存。遮挡提示仍需局部预览确认,详见 [电源符号交付](skills/kicad-schematic-design/references/power-symbols.md)。
|
|
34
34
|
|
|
@@ -36,14 +36,18 @@ KiCad CLI 自动从当前运行时安装位置或 PATH 查找,也可通过 KIC
|
|
|
36
36
|
|
|
37
37
|
含引脚的独立公共单元符号(如 SW-PTB-2_0_1)在项目副本中自动规范为单元 1,保留引脚与图形。模型摘要和导入结果通过 builderCompatibility.normalization=common_only_to_unit_1 报告;继承、空单元、异常命名及仅替代图形的符号不做自动规范。不支持的导入错误包含器件、符号、单元名称及源文件 URL。
|
|
38
38
|
|
|
39
|
-
最终概览卡片:正式预览传入 `delivery: true` 和包含全部本地库的 `projectDir`,显示页面切换、缩放、ZIP 下载。复用已有 PNG,不重新生成电路,无重新生成/详情按钮。需 KiCad Harness 的 webServer
|
|
39
|
+
最终概览卡片:正式预览传入 `delivery: true` 和包含全部本地库的 `projectDir`,显示页面切换、缩放、ZIP 下载。复用已有 PNG,不重新生成电路,无重新生成/详情按钮。需 KiCad Harness 的 webServer 与前端加载支持;不可用时仍返回本地预览路径。持久快照默认保存在当前用户家目录的 `.hq-edge/plugin-data/dsh-tool-kicad-schematic-gen/overview-cache/`,家目录通过 `os.homedir()` 动态获取;可用绝对路径 `HQ_EDGE_HOME` 覆盖 `.hq-edge` 根目录,不随 `DSH_HOME` 或插件版本变化。重启后可读取,手动删除快照后对应历史卡片下载将失效。工程、中间产物和预览保存在当前工作区;插件安装目录仅用于读取代码和模板,不读写概览快照。ZIP 为工程目录内相关设计文件与库的快照,不收集脚本、预览或外部库。
|
|
40
40
|
# 生成流程耗时优化
|
|
41
41
|
|
|
42
42
|
- 模块封装 `import` 返回 `stage: prepared`,仅表示本地库和映射准备完成;源文件默认不改写。`check_mapping` 验证来源应用映射后的状态,`check` 检查实际候选或最终原理图,避免混淆。
|
|
43
43
|
- 封装准备最多 4 并发;项目库表按项目串行写入。批次验证失败不写库,错误返回失败 assignment 索引;已验证下载在当前进程缓存 15 分钟(最多 128 项、16 MiB),退出进程后清空。瞬时网络/429/5xx 错误内部最多重试一次,格式和编号错误不自动重试。
|
|
44
|
-
-
|
|
44
|
+
- 使用现有供应商符号缓存对齐逻辑读取模块;封装仅支持当前 `(footprint ...)` 格式,旧式 `(module ...)` 明确拒绝,不自动转换。编号核对不等于尺寸或电气验证。
|
|
45
45
|
- builder 新增 `attachPinLabel`,支持显式方向、引线长度与通道错列;不推断引脚方向或自动避障。
|
|
46
46
|
- 技能目录 `scripts/run-design.mjs` 使用参数数组启动生成脚本,将退出码、执行时间和完整输出保存为 UTF-8 JSON;终端仅返回摘要,避免 PowerShell 转义、输出截断和 BOM 返工。模块封装准备提前进行,文档与脚本复用连接计划。
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
模块分析查询:`analyze_kicad_module({schematicPath, view, reference?, pin?, net?, offset?, kicadCliPath?})`。`view` 必填;下载已给分析摘要,直接查询所需 `components`、`nets` 或 `bom`。CLI/源工程默认内部解析;路径或启动失败 `CLI_START_FAILED` 时,复用 `get_eda_host_info` 返回的 kicad-cli 绝对路径,通过可选 `kicadCliPath` + `view:summary` 直接重试分析,不重新下载。成功路径后续复用,默认命令名不会覆盖自动发现;探测失败返回 `error:{code,executable}` 并区分启动、能力不支持、超时和命令失败。任意本地图采用同名 `.kicad_pro`,下载模块重启后可识别同名 `.source.kicad_pro`。详情返回 `status`、`items`、可选 `nextOffset`/`warnings`;网络按组分块,BOM 按全部物料字段与装配状态合并,器件自定义字段在指定 `reference` 时返回。部分失败结果供详情查询复用,修复后用 `view:summary` 重试。缺失 `nextOffset` 表示结束;不可用数据会明确警告。完整本地分析文件仍保留,详见 [模块分析](skills/kicad-schematic-design/references/module-analysis.md)。此接口替换旧版分析查询参数及返回结构,旧调用需同步更新。
|
|
50
|
+
|
|
51
|
+
候选预览与交付状态:普通 `render_kicad_module_preview` 可传 `projectDir`,在首版预览时校验全部页的本地封装,无需先复制库到候选目录。`footprintCheck` 返回逐页的实际解析结果,缺库时顶层 `status:error`,已渲染图片仍保留。`renderStatus` 单独表示渲染结果;`deliveryStatus` 为 `not_requested`、`ready` 或 `blocked`。正式交付失败不会返回整体成功;只有 `deliveryStatus:ready` 和有效概览才代表正式快照已创建,这仍不代表电气验证通过。
|
|
52
|
+
|
|
53
|
+
技能加载时提供 `pluginDir`、`skillDir`、`runtimePath`、`runnerPath`、`geometryPath` 等只读资源绝对路径,避免混淆插件根目录与技能根目录。资源安装目录不用于保存项目输出。失败恢复、有限重试及候选检查顺序见技能的 `references/troubleshooting.md`。
|
package/lib/client.js
CHANGED
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
message: String(o.overviewError),
|
|
20
20
|
formal: true
|
|
21
21
|
};
|
|
22
|
+
if (o.footprintCheck?.status === "blocked") return {
|
|
23
|
+
kind: "error",
|
|
24
|
+
message: String(o.footprintCheck.error || "工程本地封装检查未通过")
|
|
25
|
+
};
|
|
22
26
|
if (!v) return o.status === "error" ? {
|
|
23
27
|
kind: "error",
|
|
24
28
|
message: "未能生成预览"
|
package/lib/index.js
CHANGED
|
@@ -2494,30 +2494,7 @@ async function fetchSymbolSource(mfgId, mpn, options, signal, fetcher = fetch) {
|
|
|
2494
2494
|
};
|
|
2495
2495
|
}
|
|
2496
2496
|
function summarizeFootprint(text) {
|
|
2497
|
-
|
|
2498
|
-
if (root.head === "module") {
|
|
2499
|
-
const pads = children(root, "pad").map((pad) => {
|
|
2500
|
-
const at = children(pad, "at")[0], size = children(pad, "size")[0], layers = children(pad, "layers")[0];
|
|
2501
|
-
if (!at || !size || !layers || layers.items.length < 2 || ![1, 2].every((i) => Number.isFinite(Number(value(at, i))) && Number(value(size, i)) > 0 && Number.isFinite(Number(value(size, i))))) throw new Error("Legacy footprint pad requires finite position, positive size and layers");
|
|
2502
|
-
value(pad, 3);
|
|
2503
|
-
return {
|
|
2504
|
-
number: value(pad),
|
|
2505
|
-
type: value(pad, 2)
|
|
2506
|
-
};
|
|
2507
|
-
});
|
|
2508
|
-
if (pads.some((pad) => ![
|
|
2509
|
-
"smd",
|
|
2510
|
-
"thru_hole",
|
|
2511
|
-
"np_thru_hole",
|
|
2512
|
-
"connect"
|
|
2513
|
-
].includes(pad.type))) throw new Error("Unsupported legacy footprint pad type");
|
|
2514
|
-
return {
|
|
2515
|
-
status: "parsed",
|
|
2516
|
-
name: value(root),
|
|
2517
|
-
padCount: pads.length,
|
|
2518
|
-
pads
|
|
2519
|
-
};
|
|
2520
|
-
}
|
|
2497
|
+
if (parse(text).head !== "footprint") throw new Error("Unsupported footprint format: expected (footprint ...)");
|
|
2521
2498
|
const footprint = new BoardParser().parse(`(kicad_pcb (version 20240108) (generator dsh-tool-kicad-library) ${text})`).footprints[0];
|
|
2522
2499
|
if (!footprint) throw new Error("Huaqiu footprint file: no KiCad footprint found");
|
|
2523
2500
|
const pads = footprint.pads.map((pad) => ({
|
|
@@ -2547,7 +2524,7 @@ async function getComponentModels(mfgId, mpn, options, signal, fetcher = fetch)
|
|
|
2547
2524
|
};
|
|
2548
2525
|
}
|
|
2549
2526
|
function inspectModelText(text, url, type) {
|
|
2550
|
-
const validHeader = type === "symbol" ? /^\s*\(kicad_symbol_lib(?=\s|\()/u.test(text) : /^\s*\(
|
|
2527
|
+
const validHeader = type === "symbol" ? /^\s*\(kicad_symbol_lib(?=\s|\()/u.test(text) : /^\s*\(footprint(?=\s|\()/u.test(text);
|
|
2551
2528
|
try {
|
|
2552
2529
|
if (!validHeader) throw new Error(`Downloaded ${type} is not a KiCad model; content begins: ${text.slice(0, 160)}`);
|
|
2553
2530
|
return type === "symbol" ? summarizeSymbol(text) : summarizeFootprint(text);
|
|
@@ -2566,7 +2543,7 @@ function inspectModelText(text, url, type) {
|
|
|
2566
2543
|
//#region src/components/project-footprint.ts
|
|
2567
2544
|
function footprintModel(raw) {
|
|
2568
2545
|
const form = parse(raw), name = value(form);
|
|
2569
|
-
if (
|
|
2546
|
+
if (form.head !== "footprint" || !name || /[<>:"/\\|?*\x00-\x1f]/u.test(name) || /[. ]$/u.test(name) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(name)) throw new Error("Invalid KiCad footprint or unsafe footprint name");
|
|
2570
2547
|
const summary = summarizeFootprint(raw);
|
|
2571
2548
|
if (summary.status !== "parsed" || !summary.padCount) throw new Error("Footprint must have parsed pads");
|
|
2572
2549
|
return {
|
|
@@ -2575,18 +2552,16 @@ function footprintModel(raw) {
|
|
|
2575
2552
|
text: raw.endsWith("\n") ? raw : `${raw}\n`
|
|
2576
2553
|
};
|
|
2577
2554
|
}
|
|
2578
|
-
/** Preflight all destinations before writing; preserve existing tables and
|
|
2555
|
+
/** Preflight all destinations before writing; preserve existing tables and use the current project library layout. */
|
|
2579
2556
|
async function prepareProjectFootprint(project, nickname, raw, fs, signal) {
|
|
2580
2557
|
const model = footprintModel(raw);
|
|
2581
|
-
const
|
|
2582
|
-
const legacyExists = await fs.stat(legacy, signal) !== void 0;
|
|
2583
|
-
const footprint = await fs.resolve(join(project.displayPath, ...legacyExists ? [] : ["libraries", "footprints"], `${nickname}.pretty`, `${model.name}.kicad_mod`), { signal });
|
|
2558
|
+
const footprint = await fs.resolve(join(project.displayPath, "libraries", "footprints", `${nickname}.pretty`, `${model.name}.kicad_mod`), { signal });
|
|
2584
2559
|
const table = await fs.resolve(join(project.displayPath, "fp-lib-table"), { signal });
|
|
2585
2560
|
if (!fs.contains(project, footprint) || !fs.contains(project, table)) throw new Error("Footprint targets must stay inside projectDir");
|
|
2586
2561
|
const info = await fs.stat(footprint, signal), tableInfo = await fs.stat(table, signal);
|
|
2587
2562
|
if (info && (info.type !== "file" || await fs.readText(footprint, signal) !== model.text)) throw new Error("Footprint already exists with different content");
|
|
2588
2563
|
if (tableInfo && tableInfo.type !== "file") throw new Error("fp-lib-table must be a file");
|
|
2589
|
-
const uri = `\${KIPRJMOD}
|
|
2564
|
+
const uri = `\${KIPRJMOD}/libraries/footprints/${nickname}.pretty`;
|
|
2590
2565
|
const entry = ` (lib (name ${quote(nickname)})(type "KiCad")(uri ${quote(uri)})(options "")(descr ""))`;
|
|
2591
2566
|
const old = tableInfo ? await fs.readText(table, signal) : null;
|
|
2592
2567
|
let next = null;
|
|
@@ -3399,10 +3374,8 @@ async function importSymbol(mfgId, mpn, projectDir, workspaceRoot, options, fs,
|
|
|
3399
3374
|
const source = preparedSource ?? await fetchSymbolSource(mfgId, mpn, options, signal, fetcher);
|
|
3400
3375
|
if (source.symbol.name.length === 0 || source.symbol.name.includes(":")) throw new Error("upstream Symbol name must be non-empty and must not contain a colon");
|
|
3401
3376
|
const nickname = libraryName(mfgId, mpn);
|
|
3402
|
-
const
|
|
3403
|
-
const
|
|
3404
|
-
const library = legacyExists ? legacy : await fs.resolve(join(project.displayPath, "libraries", "symbols", `${nickname}.kicad_sym`), { signal });
|
|
3405
|
-
const uri = `\${KIPRJMOD}/${legacyExists ? "" : "libraries/symbols/"}${nickname}.kicad_sym`;
|
|
3377
|
+
const library = await fs.resolve(join(project.displayPath, "libraries", "symbols", `${nickname}.kicad_sym`), { signal });
|
|
3378
|
+
const uri = `\${KIPRJMOD}/libraries/symbols/${nickname}.kicad_sym`;
|
|
3406
3379
|
const table = await fs.resolve(join(project.displayPath, "sym-lib-table"), { signal });
|
|
3407
3380
|
if (!fs.contains(project, library) || !fs.contains(project, table)) throw new Error("project library targets must stay inside projectDir");
|
|
3408
3381
|
const rawText = source.text.endsWith("\n") ? source.text : `${source.text}\n`;
|
|
@@ -3444,10 +3417,10 @@ async function importSymbol(mfgId, mpn, projectDir, workspaceRoot, options, fs,
|
|
|
3444
3417
|
}
|
|
3445
3418
|
//#endregion
|
|
3446
3419
|
//#region src/components/identity.ts
|
|
3447
|
-
/**
|
|
3420
|
+
/** Parse the current manufacturer_id/MPN contract. */
|
|
3448
3421
|
function partIdentity(args) {
|
|
3449
|
-
|
|
3450
|
-
|
|
3422
|
+
if ("mfgId" in args) throw new Error("Unsupported parameter mfgId; use manufacturer_id");
|
|
3423
|
+
const id = args.manufacturer_id;
|
|
3451
3424
|
if (typeof id !== "string" || !/^\d{1,20}$/.test(id)) throw new Error("manufacturer_id must contain 1 to 20 digits");
|
|
3452
3425
|
if (typeof args.mpn !== "string" || !args.mpn.trim() || args.mpn.length > 120) throw new Error("mpn must contain 1 to 120 characters");
|
|
3453
3426
|
return {
|
|
@@ -3486,7 +3459,7 @@ async function importComponent(mfgId, mpn, projectDir, workspaceRoot, options, f
|
|
|
3486
3459
|
if (!urls.symbol || !urls.footprint) throw new Error("COMPONENT_MODELS_REQUIRED: both symbol and footprint URLs are required");
|
|
3487
3460
|
const [source, rawFootprint] = await Promise.all([fetchSymbolSource(mfgId, mpn, options, signal, cachedFetch), fetchModelText(urls.footprint, "footprint", options, signal, cachedFetch)]);
|
|
3488
3461
|
const form = parse(rawFootprint);
|
|
3489
|
-
if (
|
|
3462
|
+
if (form.head !== "footprint") throw new Error("Downloaded footprint is not a KiCad footprint");
|
|
3490
3463
|
const footprintName = value(form);
|
|
3491
3464
|
if (!footprintName || /[<>:"/\\|?*\x00-\x1f]/u.test(footprintName) || /[. ]$/u.test(footprintName) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(footprintName)) throw new Error("Footprint name is not a safe local filename");
|
|
3492
3465
|
const summary = summarizeFootprint(rawFootprint);
|
|
@@ -3500,9 +3473,7 @@ async function importComponent(mfgId, mpn, projectDir, workspaceRoot, options, f
|
|
|
3500
3473
|
if (compatibility.status !== "ready") throw new Error(`SYMBOL_UNITS_UNSUPPORTED: ${mfgId}:${mpn}; symbol ${source.symbol.name}; sections [${(source.symbol.children ?? []).map((child) => child.name).join(", ")}]; source ${source.sourceUrl}; ${compatibility.nextAction}`);
|
|
3501
3474
|
const nickname = libraryName(mfgId, mpn);
|
|
3502
3475
|
const footprintText = rawFootprint.endsWith("\n") ? rawFootprint : `${rawFootprint}\n`;
|
|
3503
|
-
const
|
|
3504
|
-
const legacyExists = await fs.stat(legacy, signal) !== void 0;
|
|
3505
|
-
const footprint = await fs.resolve(join(project.displayPath, ...legacyExists ? [] : ["libraries", "footprints"], `${nickname}.pretty`, `${footprintName}.kicad_mod`), { signal });
|
|
3476
|
+
const footprint = await fs.resolve(join(project.displayPath, "libraries", "footprints", `${nickname}.pretty`, `${footprintName}.kicad_mod`), { signal });
|
|
3506
3477
|
const table = await fs.resolve(join(project.displayPath, "fp-lib-table"), { signal });
|
|
3507
3478
|
if (!fs.contains(project, footprint) || !fs.contains(project, table)) throw new Error("Footprint targets must stay inside projectDir");
|
|
3508
3479
|
const info = await fs.stat(footprint, signal);
|
|
@@ -3510,7 +3481,7 @@ async function importComponent(mfgId, mpn, projectDir, workspaceRoot, options, f
|
|
|
3510
3481
|
const tableInfo = await fs.stat(table, signal);
|
|
3511
3482
|
if (tableInfo && tableInfo.type !== "file") throw new Error("fp-lib-table must be a regular file");
|
|
3512
3483
|
const oldTable = tableInfo ? await fs.readText(table, signal) : null;
|
|
3513
|
-
const uri = `\${KIPRJMOD}
|
|
3484
|
+
const uri = `\${KIPRJMOD}/libraries/footprints/${nickname}.pretty`;
|
|
3514
3485
|
const entry = ` (lib (name ${quote(nickname)})(type "KiCad")(uri ${quote(uri)})(options "")(descr ""))`;
|
|
3515
3486
|
let nextTable;
|
|
3516
3487
|
if (oldTable === null) nextTable = `(fp_lib_table\n (version 7)\n${entry}\n)\n`;
|
|
@@ -5491,13 +5462,10 @@ function inspectPinConnections(objects, reference, number) {
|
|
|
5491
5462
|
//#region src/schematic/overview.ts
|
|
5492
5463
|
/** Immutable, bounded snapshots for the browser; never accepts browser file paths. */
|
|
5493
5464
|
const OVERVIEW_ROUTE = "/api/v1/kicad-schematic-overview";
|
|
5494
|
-
|
|
5495
|
-
/** Store generated data outside installed code, including read-only plugin installations. */
|
|
5465
|
+
/** Persist snapshots across sessions and runtime upgrades under the current user's HQ Edge home. */
|
|
5496
5466
|
function overviewCacheDirectory(env = process.env) {
|
|
5497
|
-
const home = env.
|
|
5498
|
-
|
|
5499
|
-
const userData = env.LOCALAPPDATA && isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : join(homedir(), ".local", "share");
|
|
5500
|
-
return join(userData, "deepseek-harness", "dsh-tool-kicad-schematic-gen", "overview-cache");
|
|
5467
|
+
const home = env.HQ_EDGE_HOME && isAbsolute(env.HQ_EDGE_HOME) ? env.HQ_EDGE_HOME : join(homedir(), ".hq-edge");
|
|
5468
|
+
return join(home, "plugin-data", "dsh-tool-kicad-schematic-gen", "overview-cache");
|
|
5501
5469
|
}
|
|
5502
5470
|
const MAX_BYTES = 67108864;
|
|
5503
5471
|
async function createOverview(input, store = overviewCacheDirectory()) {
|
|
@@ -5588,7 +5556,7 @@ async function createOverview(input, store = overviewCacheDirectory()) {
|
|
|
5588
5556
|
size: zip.length
|
|
5589
5557
|
};
|
|
5590
5558
|
}
|
|
5591
|
-
function overviewHandler(store = overviewCacheDirectory()
|
|
5559
|
+
function overviewHandler(store = overviewCacheDirectory()) {
|
|
5592
5560
|
return async (req, res) => {
|
|
5593
5561
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
5594
5562
|
res.writeHead(405);
|
|
@@ -5603,13 +5571,7 @@ function overviewHandler(store = overviewCacheDirectory(), legacyStore = store =
|
|
|
5603
5571
|
return;
|
|
5604
5572
|
}
|
|
5605
5573
|
try {
|
|
5606
|
-
|
|
5607
|
-
try {
|
|
5608
|
-
bytes = await readFile(join(store, match[1], match[2]));
|
|
5609
|
-
} catch (error) {
|
|
5610
|
-
if (error.code !== "ENOENT" || !legacyStore) throw error;
|
|
5611
|
-
bytes = await readFile(join(legacyStore, match[1], match[2]));
|
|
5612
|
-
}
|
|
5574
|
+
const bytes = await readFile(join(store, match[1], match[2]));
|
|
5613
5575
|
res.writeHead(200, {
|
|
5614
5576
|
"content-type": match[2] === "project.zip" ? "application/zip" : match[2] === "placement.json" ? "application/json" : "image/png",
|
|
5615
5577
|
"content-length": bytes.length,
|
|
@@ -5929,7 +5891,7 @@ function registerSchematicTools(ctx, config) {
|
|
|
5929
5891
|
},
|
|
5930
5892
|
projectDir: {
|
|
5931
5893
|
type: "string",
|
|
5932
|
-
description: "
|
|
5894
|
+
description: "Project folder containing local libraries and library tables. On ordinary candidate previews, pass this to check footprints against the prepared project before publishing. With delivery:true defaults to the schematic folder and must also contain all sheets. Must stay inside workspace."
|
|
5933
5895
|
},
|
|
5934
5896
|
regions: {
|
|
5935
5897
|
type: "array",
|
|
@@ -5990,6 +5952,26 @@ function registerSchematicTools(ctx, config) {
|
|
|
5990
5952
|
]
|
|
5991
5953
|
}] },
|
|
5992
5954
|
overviewError: { oneOf: [{ type: "string" }, { type: "null" }] },
|
|
5955
|
+
renderStatus: {
|
|
5956
|
+
type: "string",
|
|
5957
|
+
enum: [
|
|
5958
|
+
"ok",
|
|
5959
|
+
"partial",
|
|
5960
|
+
"error"
|
|
5961
|
+
]
|
|
5962
|
+
},
|
|
5963
|
+
deliveryStatus: {
|
|
5964
|
+
type: "string",
|
|
5965
|
+
enum: [
|
|
5966
|
+
"not_requested",
|
|
5967
|
+
"ready",
|
|
5968
|
+
"blocked"
|
|
5969
|
+
]
|
|
5970
|
+
},
|
|
5971
|
+
footprintCheck: { oneOf: [{ type: "null" }, {
|
|
5972
|
+
type: "object",
|
|
5973
|
+
additionalProperties: true
|
|
5974
|
+
}] },
|
|
5993
5975
|
manifestPath: { type: "string" },
|
|
5994
5976
|
cached: { type: "boolean" },
|
|
5995
5977
|
regionManifestPath: { type: "string" },
|
|
@@ -6081,6 +6063,9 @@ function registerSchematicTools(ctx, config) {
|
|
|
6081
6063
|
"pages",
|
|
6082
6064
|
"failures",
|
|
6083
6065
|
"status",
|
|
6066
|
+
"renderStatus",
|
|
6067
|
+
"deliveryStatus",
|
|
6068
|
+
"footprintCheck",
|
|
6084
6069
|
"rootImagePath",
|
|
6085
6070
|
"omittedPages",
|
|
6086
6071
|
"readImageInstruction",
|
|
@@ -6105,7 +6090,6 @@ function registerSchematicTools(ctx, config) {
|
|
|
6105
6090
|
], "render_kicad_module_preview");
|
|
6106
6091
|
const renderConfig = previewConfig(config, input.kicadCliPath);
|
|
6107
6092
|
if (input.delivery !== void 0 && typeof input.delivery !== "boolean") throw new Error("delivery must be boolean");
|
|
6108
|
-
if (input.projectDir !== void 0 && input.delivery !== true) throw new Error("projectDir requires delivery:true");
|
|
6109
6093
|
const workspace = workspaceRoot(exec.agent);
|
|
6110
6094
|
const source = await readWorkspaceSchematic(ctx.fs, workspace, requiredString(input, "schematicPath"), config.maxSchematicBytes, exec.signal);
|
|
6111
6095
|
const requestedDir = input.outputDir === void 0 ? join(dirname(source.path), "previews") : requiredString(input, "outputDir");
|
|
@@ -6115,6 +6099,39 @@ function registerSchematicTools(ctx, config) {
|
|
|
6115
6099
|
const pageNames = input.pageNames === void 0 ? [] : parseUnconnectedPorts(input.pageNames, "pageNames");
|
|
6116
6100
|
if (pageNames.length > config.previewMaxPages) throw new Error(`code=PREVIEW_PAGE_LIMIT; retryUnchanged=false; request at most ${config.previewMaxPages} pageNames per call.`);
|
|
6117
6101
|
const regions = resolvePreviewRegions(input.regions, source.text, basename(source.path, ".kicad_sch"), config.flatListLimit);
|
|
6102
|
+
let footprintCheck = null;
|
|
6103
|
+
let checkedProject;
|
|
6104
|
+
if (input.delivery === true || input.projectDir !== void 0) {
|
|
6105
|
+
footprintCheck = {
|
|
6106
|
+
status: "ready",
|
|
6107
|
+
sheets: [],
|
|
6108
|
+
error: null
|
|
6109
|
+
};
|
|
6110
|
+
try {
|
|
6111
|
+
const project = input.projectDir === void 0 ? dirname(source.path) : requiredString(input, "projectDir");
|
|
6112
|
+
const [root, resolved] = await Promise.all([ctx.fs.resolve(workspace, { signal: exec.signal }), ctx.fs.resolve(project, {
|
|
6113
|
+
cwd: workspace,
|
|
6114
|
+
signal: exec.signal
|
|
6115
|
+
})]);
|
|
6116
|
+
if (!ctx.fs.contains(root, resolved) || (await ctx.fs.stat(resolved, exec.signal))?.type !== "directory") throw new Error("projectDir must be an existing directory inside the workspace");
|
|
6117
|
+
checkedProject = resolved.displayPath;
|
|
6118
|
+
for (const item of sources) {
|
|
6119
|
+
const sheet = item.schematicPath === source.path ? source : await readWorkspaceSchematic(ctx.fs, workspace, item.schematicPath, config.maxSchematicBytes, exec.signal);
|
|
6120
|
+
if (input.delivery === true) assertSchematicGrid(sheet.text);
|
|
6121
|
+
const audit = await auditFootprints(sheet.text, checkedProject, ctx.fs, exec.signal);
|
|
6122
|
+
footprintCheck.sheets.push({
|
|
6123
|
+
schematicPath: sheet.path,
|
|
6124
|
+
...audit
|
|
6125
|
+
});
|
|
6126
|
+
}
|
|
6127
|
+
const missing = footprintCheck.sheets.filter((sheet) => sheet.status !== "ready");
|
|
6128
|
+
if (missing.length) throw new Error(`FOOTPRINTS_UNRESOLVED: ${missing.map((sheet) => `${sheet.schematicPath}: ${sheet.unresolved.map((part) => part.reference).join(", ")}`).join("; ")}. Prepare project-local footprints and persist script mappings before publishing; do not rebuild unchanged geometry to repair missing libraries.`);
|
|
6129
|
+
} catch (error) {
|
|
6130
|
+
exec.signal.throwIfAborted();
|
|
6131
|
+
footprintCheck.status = "blocked";
|
|
6132
|
+
footprintCheck.error = error instanceof Error ? error.message : String(error);
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
6118
6135
|
const rendered = await renderModulePreview(outputDir, source.path, revision, renderConfig, exec.signal, sources, void 0, pageNames);
|
|
6119
6136
|
const selectedRegions = input.regions === void 0 ? regions.filter((r) => rendered.pages.some((p) => p.exportedPageName === r.pageName)) : regions;
|
|
6120
6137
|
const crops = await renderPreviewRegions(rendered.pages, selectedRegions, revision, rendered.manifestPath, config, exec.signal);
|
|
@@ -6122,35 +6139,31 @@ function registerSchematicTools(ctx, config) {
|
|
|
6122
6139
|
const omittedPages = sources.filter((item) => !rendered.pages.some((page) => page.exportedPageName === item.exportedPageName)).map((item) => item.exportedPageName);
|
|
6123
6140
|
let overview = null, overviewError = null;
|
|
6124
6141
|
if (input.delivery === true) {
|
|
6125
|
-
if (
|
|
6142
|
+
if (footprintCheck?.status === "blocked") overviewError = footprintCheck.error;
|
|
6143
|
+
else if (!overviewAvailable) overviewError = "Preview card service unavailable; use returned local preview paths.";
|
|
6126
6144
|
else if (!rendered.pages.length) overviewError = "No rendered pages available for the overview.";
|
|
6127
6145
|
else try {
|
|
6128
|
-
const project = input.projectDir === void 0 ? dirname(source.path) : requiredString(input, "projectDir");
|
|
6129
|
-
const resolved = await ctx.fs.resolve(project, {
|
|
6130
|
-
cwd: workspace,
|
|
6131
|
-
signal: exec.signal
|
|
6132
|
-
});
|
|
6133
|
-
for (const item of sources) {
|
|
6134
|
-
const sheet = item.schematicPath === source.path ? source : await readWorkspaceSchematic(ctx.fs, workspace, item.schematicPath, config.maxSchematicBytes, exec.signal);
|
|
6135
|
-
assertSchematicGrid(sheet.text);
|
|
6136
|
-
const footprints = await auditFootprints(sheet.text, resolved.displayPath, ctx.fs, exec.signal);
|
|
6137
|
-
if (footprints.status !== "ready") throw new Error(`FOOTPRINTS_UNRESOLVED: ${footprints.unresolved.length} of ${footprints.total} physical instances in ${item.schematicPath} lack project-local footprints. Run localize_kicad_module_footprints check/import and persist script mappings before delivery.`);
|
|
6138
|
-
}
|
|
6139
6146
|
overview = await createOverview({
|
|
6140
6147
|
workspace,
|
|
6141
|
-
projectDir:
|
|
6148
|
+
projectDir: checkedProject,
|
|
6142
6149
|
schematicPath: source.path,
|
|
6143
6150
|
revision,
|
|
6144
6151
|
pages: rendered.pages,
|
|
6145
6152
|
sources
|
|
6146
6153
|
});
|
|
6147
6154
|
} catch (error) {
|
|
6155
|
+
exec.signal.throwIfAborted();
|
|
6148
6156
|
overviewError = error instanceof Error ? error.message : String(error);
|
|
6149
6157
|
}
|
|
6150
6158
|
}
|
|
6159
|
+
const renderStatus = rendered.pages.length === 0 ? "error" : rendered.truncated || failures.length > 0 ? "partial" : "ok";
|
|
6160
|
+
const deliveryStatus = input.delivery !== true ? "not_requested" : overview ? "ready" : "blocked";
|
|
6151
6161
|
return {
|
|
6152
6162
|
overview,
|
|
6153
6163
|
overviewError,
|
|
6164
|
+
renderStatus,
|
|
6165
|
+
deliveryStatus,
|
|
6166
|
+
footprintCheck,
|
|
6154
6167
|
schematicPath: source.path,
|
|
6155
6168
|
revision,
|
|
6156
6169
|
outputDir,
|
|
@@ -6162,7 +6175,7 @@ function registerSchematicTools(ctx, config) {
|
|
|
6162
6175
|
failures,
|
|
6163
6176
|
regionImages: crops.regionImages,
|
|
6164
6177
|
regionManifestPath: crops.regionManifestPath,
|
|
6165
|
-
status:
|
|
6178
|
+
status: deliveryStatus === "blocked" || footprintCheck?.status === "blocked" ? "error" : renderStatus,
|
|
6166
6179
|
rootImagePath: rendered.pages.find((page) => page.pageSchematicPath === source.path || page.exportedPageName === basename(source.path, ".kicad_sch"))?.pngPath ?? null,
|
|
6167
6180
|
omittedPages,
|
|
6168
6181
|
readImageInstruction: "For module detail read exact regionImages[].pngPath first; each image includes millimetre bounds, source and revision in regionManifestPath. Read rootImagePath for overall layout, or an exact pages[].pngPath. Do not write crop/zoom scripts for existing region images. Only listed PNGs exist; never replace an SVG extension or construct a PNG path. Check failures first: do not repeat failed pages unchanged or export a netlist to repair a preview. For unrendered pages not listed in failures, call render_kicad_module_preview with the same schematicPath and pageNames from omittedPages. Match the image to revision and its pageSchematicPath using manifestPath, pageIndex, and exportedPageName. A null pageSchematicPath means the page name could not be matched; do not infer its child file. A preview is not netlist evidence."
|
|
@@ -6696,6 +6709,20 @@ const skillName = "kicad-schematic-design";
|
|
|
6696
6709
|
const providerName = `bundled:${skillName}`;
|
|
6697
6710
|
const directory = fileURLToPath(new URL(`../skills/${skillName}/`, import.meta.url));
|
|
6698
6711
|
const skillFile = new URL(`../skills/${skillName}/SKILL.md`, import.meta.url);
|
|
6712
|
+
/** Read-only installed resources; project outputs must use the session workspace. */
|
|
6713
|
+
function skillResourcePaths() {
|
|
6714
|
+
return {
|
|
6715
|
+
pluginDir: fileURLToPath(new URL("../", import.meta.url)),
|
|
6716
|
+
skillDir: directory,
|
|
6717
|
+
referencesDir: join(directory, "references"),
|
|
6718
|
+
assetsDir: join(directory, "assets"),
|
|
6719
|
+
runtimePath: join(directory, "assets", "runtime.mjs"),
|
|
6720
|
+
runnerPath: join(directory, "scripts", "run-design.mjs"),
|
|
6721
|
+
drawingPatternsPath: join(directory, "references", "drawing-patterns.md"),
|
|
6722
|
+
geometryPath: join(directory, "assets", "drawing-patterns", "geometry.mjs"),
|
|
6723
|
+
templateDir: join(directory, "templates")
|
|
6724
|
+
};
|
|
6725
|
+
}
|
|
6699
6726
|
function parseSkill(raw) {
|
|
6700
6727
|
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/u.exec(raw);
|
|
6701
6728
|
if (!frontmatter) throw new Error(`${skillName}: missing skill frontmatter`);
|
|
@@ -6746,7 +6773,7 @@ function apply$1(ctx, kicadCliPath) {
|
|
|
6746
6773
|
provider: providerName,
|
|
6747
6774
|
source: candidate.source,
|
|
6748
6775
|
resourceBase,
|
|
6749
|
-
content: `${loaded.content}\n\n## 当前安装路径(运行时生成)\n\n
|
|
6776
|
+
content: `${loaded.content}\n\n## 当前安装路径(运行时生成)\n\n只读资源路径清单(直接使用,不从 pluginDir 猜测技能资源路径;输出另存当前工作区):\n\n\`\`\`json\n${JSON.stringify(skillResourcePaths(), null, 2)}\n\`\`\`\n\n项目脚本设置 DSH_SCHEMATIC_PLUGIN_DIR=${JSON.stringify(skillResourcePaths().pluginDir)}。CLI=${JSON.stringify(paths.kicadCliPath)}。${paths.symbolDir ? `KICAD_SYMBOL_DIR=${JSON.stringify(paths.symbolDir)}。` : "未找到标准符号库;使用项目符号或配置 KICAD_SYMBOL_DIR。不要扫描或猜测其他安装目录;随包参考符号不提供实际器件的本地封装。"}`
|
|
6750
6777
|
};
|
|
6751
6778
|
}
|
|
6752
6779
|
})));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huaqiu/dsh-tool-kicad-schematic-gen",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"description": "KiCad 原理图设计:电路模块搜索、器件模型与手册、符号导入、编辑预览、脚本构建及设计技能",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"pdfjs-dist": "6.3.289",
|
|
33
33
|
"sharp": "0.35.4",
|
|
34
34
|
"undici": "^8.10.0",
|
|
35
|
-
"@huaqiu/dsh-plugin-log": "0.6.
|
|
36
|
-
"@huaqiu/dsh-sidebar-entry": "0.6.
|
|
35
|
+
"@huaqiu/dsh-plugin-log": "0.6.10",
|
|
36
|
+
"@huaqiu/dsh-sidebar-entry": "0.6.10"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
@@ -13,6 +13,16 @@ description: Before a new schematic, ask the user to confirm requirements and wa
|
|
|
13
13
|
|
|
14
14
|
自动分析若报 CLI_START_FAILED,复用 get_eda_host_info 的 installation.executables 中名为 kicad-cli 的 path,传入 analyze_kicad_module({schematicPath,view:"summary",kicadCliPath}) 重试;没有已有宿主信息才获取一次。不要重新下载模块、扫描配置目录或把不支持的导出命令当作路径问题。后续查询复用成功路径,省略 kicadCliPath。
|
|
15
15
|
|
|
16
|
+
## 执行主线
|
|
17
|
+
|
|
18
|
+
需求确认 → 保存并读回硬件方案 → 检查模块与准备全部实际器件库 → 按连接计划编写脚本 → 候选预览与本地封装检查 → 集中修复并同步方案 → 发布与正式概览。方案阶段的选型核实照常进行,方案未保存读回前不编写生成脚本;不增加额外审批。
|
|
19
|
+
|
|
20
|
+
- 路径直接使用末尾运行时提供的 `skillDir/runtimePath/runnerPath/geometryPath` 等绝对路径。本文的 assets/、references/、scripts/ 均相对于 skillDir,不是 pluginDir。只读取本次需要的接口、所选画法及其直接依赖,不递归列出整个技能目录。
|
|
21
|
+
- 同一项目已读取的接口和成功查询沿用;独立的只读资料请求可批量执行。写入同一工程库表的导入按工具并发约定执行,不盲目并发修改库表。
|
|
22
|
+
- 首版前准备全部物理器件(含阻容与连接器)的真实本地符号、封装及库表。已有复用模块先核对映射;参考符号和非空 Footprint 名称都不代表本地封装已就绪。
|
|
23
|
+
- 第一版候选普通预览传 `projectDir` 指向已准备库表的正式工程目录,同时检查 `footprintCheck`;修正未解析引用后再发布。正式交付要求 `deliveryStatus:ready` 且 overview 非空;`renderStatus:ok` 只代表渲染成功。
|
|
24
|
+
- 读取或导入失败时按[错误分类与恢复](references/troubleshooting.md)改变下一步,不原样重复确定性错误。实际器件变更时同步方案/BOM,再读回版本并构建。已使用任务清单时,在最后一次正式概览之前更新实际完成状态;未完成项说明原因。
|
|
25
|
+
|
|
16
26
|
## 1. 明确需求与原理图形式
|
|
17
27
|
|
|
18
28
|
新建原理图项目必须先执行 hardware-design-brief 的“方案前需求确认”:展示已知目标、交付范围、关键缺口和推荐选项,用 `ask_user_question` 提问并等待真实用户回复;即使能自行作假设也不能跳过。工具不可用时在对话中提问并结束本轮等待。未回复、取消、超时或默认选择不算确认,期间只可只读核实资料,不得写正式方案、生成脚本或电路副本。已确认的同一项目需求可沿用,不反复确认。需求回复应在现有 PRD/精简方案中记录实际来源;有文档和 hash 不等于需求已经确认。
|
|
@@ -45,9 +55,9 @@ description: Before a new schematic, ask the user to confirm requirements and wa
|
|
|
45
55
|
|
|
46
56
|
对选定的复用模块完成[模块封装本地化](references/module-footprints.md):先 list 原实例,优先利用模块下载返回的 footprints,缺失时搜索精确器件并只下载对应封装;保存 mappingPath,并在设计脚本的 importModule 中传 footprintMappingPath。已有符号和电路保持不变。最终 project 中每个需要上板的实例都必须指向实际存在的本地封装,不能保留未注册的 kicad_api_lib 引用;电源符号不要求封装。来源内容变化时重新生成映射,布局重跑时复用记录。
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
封装准备从模块确定后开始,首版构建前完成实际器件库和来源映射,不留到交付前集中补齐。`import.stage=prepared` 只表示库与映射就绪,来源未改写时用 `check_mapping` 检查准备状态;`check` 留给已应用映射的实际候选/最终原理图,避免把源文件 resolved:0 当成下载失败。先利用 list 的 dnp、端口工具和器件引脚信息明确真实接法,再共用连接计划生成脚本与文档引脚表。
|
|
49
59
|
|
|
50
|
-
项目本地器件库统一归类:符号写入 `project/libraries/symbols/*.kicad_sym`,封装写入 `project/libraries/footprints/*.pretty/`;`sym-lib-table` 和 `fp-lib-table` 保留在 project 根目录,通过 `${KIPRJMOD}/libraries/...` 注册。复用导入工具返回的 libraryPath、libId、footprintId,不另在根目录创建副本。设计脚本使用相对于自身文件位置的路径定位 project,并保留整个 libraries 目录用于打包和后续 PCB
|
|
60
|
+
项目本地器件库统一归类:符号写入 `project/libraries/symbols/*.kicad_sym`,封装写入 `project/libraries/footprints/*.pretty/`;`sym-lib-table` 和 `fp-lib-table` 保留在 project 根目录,通过 `${KIPRJMOD}/libraries/...` 注册。复用导入工具返回的 libraryPath、libId、footprintId,不另在根目录创建副本。设计脚本使用相对于自身文件位置的路径定位 project,并保留整个 libraries 目录用于打包和后续 PCB。不再自动复用根目录旧库;遇到同名旧库表路径冲突时明确修正库表及脚本,不依赖历史目录回退。
|
|
51
61
|
|
|
52
62
|
专门的器件搜索由 KiCad 内置插件 `@huaqiu/dsh-tool-part-search` 提供。用 `search_hqsch_parts({query, page, page_size, require_eda_model: true, requirements: {symbol: true, footprint: true}})` 搜索型号或功能规格,结果在 `items` 中,支持分页。需要完整参数、分类、文档或标签时,用选定器件的 `manufacturer.id` 和精确型号调用 `get_hqsch_part({manufacturer_id, mpn})`。`get_hqsch_part_models({manufacturer_id, mpn})` 只返回符号、封装、3D 等模型链接,不下载或解析;检查实际引脚和焊盘仍用原理图插件的 `get_kicad_component_models({manufacturer_id, mpn})`。仅在需要采购信息时调用 `get_hqsch_supply_chain({parts: [{manufacturer_id, mpn}]})`,空报价不代表市场无库存。搜索中的模型标记可能由筛选条件推导,不能据此认定模型一定存在或不存在;以详情、模型链接及实际解析结果核对。搜索插件关闭或不可用时说明条件,不绕过开关;可继续检查已有模块,缺少在线符号的部分按本节回退规则处理。
|
|
53
63
|
|
|
@@ -74,7 +84,7 @@ description: Before a new schematic, ask the user to confirm requirements and wa
|
|
|
74
84
|
|
|
75
85
|
从零编写或补充电路时读取 [编程接口](references/programming.md) 与 [模块脚本组织](references/modular-scripts.md)。模型决定器件、连接和布局,现有 builder.js 处理文件格式与真实引脚坐标。多功能电路采用 design.mjs 主入口和 modules/ 下每框独立脚本,共享器件数据与引用编号;先放置全部器件,再布线,最后统一保存。下载复用模块和新编写模块可同时进入同一个 Schematic;此组合优先按 [混合整合范例](references/mixed-design.md) 编写主入口,导入模块后将返回的目标引用编号登记到共享分配器,再放置新增器件。只修受影响模块和连接,避免重新读取全部代码。默认不创建 smoke.mjs 或单电阻测试工程;直接生成实际候选并调用预览。只有库接口发生变化或明确的内部错误才做最小复现。先用生成器返回的全部外框坐标检查页面范围与框间重叠,集中调整一次后再预览,不为打印坐标重复生成候选。
|
|
76
86
|
|
|
77
|
-
新增 LED
|
|
87
|
+
新增 LED、上拉/复位 RC、去耦、三脚或带 EN 的 LDO、I²C 上拉、无源晶振负载电容、异步 Buck/Boost DC-DC、MCU 最小系统、USB Type-C 设备端、MOSFET 低侧开关、SWD 接口等局部电路,或排列芯片/连接器的信号标签时,先按需读取 [局部电路画法索引](references/drawing-patterns.md),选择已确认拓扑匹配的简短代码;不熟悉画法才读取对应参考图。局部单元放在现有功能框内,共用编号和两阶段 place/connect,不各自建框或保存工程。代码依照实际导入符号的引脚、方向和属性调整,不能照抄范例取值或用画法替代电气设计。复用模块布局已经清楚时保留,不为套范例重画。绘制复位/使能 RC 或并列启动按键时,读取 [复位与启动画法](references/drawing-patterns/reset-enable.md):上拉与按键竖向对齐,对地电容放节点右下方、地向下,避免横放按键的回绕线;BOOT 不自动复制复位电容。LDO 输入电容放在 VIN 一侧、输出电容放在 VOUT 一侧,优先短导线直接连接,不把所属外围集中到框下方;芯片去耦同样归属到供电脚附近。MCU 的复位 RC、BOOT 配置优先靠近所属引脚直接连线,避免用同名标签把紧邻外围拆成孤立支路;晶振位于单独支路时在两端及 MCU 对应脚使用成对同名标签,避免跨区长线;按已确认时钟方案选择内部 RC 或外部无源晶振范例。先组织框内主信号流向、对齐器件和属性,再调整外框与整页位置;同一画法与图片在本轮已经读过就复用结论。
|
|
78
88
|
|
|
79
89
|
同侧多个信号优先用编程接口中的 attachPinLabels 批量短直引出:左右水平、上下竖排,导线外端同列/同行,名称默认排在引线上方或沿竖线排列;不随名称长度逐根改变线长,也不默认把上下引脚扇出成阶梯。电源、地、NC 和已有连接单独处理;普通单点或确需折线时才用 attachPinLabel。执行用 scripts/run-design.mjs 保存 UTF-8 构建报告,正常项目不读取编译库源码猜接口。同一问题连续两轮仍未改善时,改用更宽外框或不同引线通道,集中处理明确遮挡;不跳过电气问题或无法辨认的内容。
|
|
80
90
|
|
|
@@ -90,4 +100,4 @@ description: Before a new schematic, ask the user to confirm requirements and wa
|
|
|
90
100
|
|
|
91
101
|
候选通过后发布到固定 project/,只替换本设计生成的工程文件和已列明子页,保留符号库、来源和其他工程文件。正式工程使用相对子页路径,再生成正式预览;安全清理本轮候选。交付正式原理图、工程配置、所需符号库、脚本、来源与预览,说明复用/修改/新设计的模块及未核实条件。允许为模块理解只读提取并缓存网表/BOM;默认不运行 ERC、不额外进行网表接线比对、不编写端点审计、不生成 PCB;用户明确要求时再执行。预览和连接计划不能称为电气验证通过。
|
|
92
102
|
|
|
93
|
-
先在候选上完成最后一次读图、修复、封装检查和发布,再生成正式概览;不要先 delivery 再读取图片继续布局修改。若发现新问题,使用普通预览修复完成后再正式交付。同一轮只展示最后一次有效正式预览,旧 ZIP 快照仍保留。正式概览必须是本轮最后一个工具操作;清理、检查和读图都在它之前完成。之后若继续调用任何工具,必须重新正式交付;完成正式交付后直接输出最终说明。最终正式工程的预览调用 `render_kicad_module_preview({schematicPath: 正式原理图路径, projectDir: 正式project目录, delivery: true})`,可复用同版本渲染缓存,在本轮正常完成后,于最终回复后的轮次尾部显示原理图概览、页面切换、缩放和工程 ZIP 下载卡片;执行期间仅保存工具结果,不立即展示卡片。中间布局检查不传 delivery。projectDir 必须同时包含根图、子页和工程所需本地库;ZIP 收集该目录内原理图、工程配置、符号、封装、库表及3D
|
|
103
|
+
先在候选上完成最后一次读图、修复、封装检查和发布,再生成正式概览;不要先 delivery 再读取图片继续布局修改。若发现新问题,使用普通预览修复完成后再正式交付。同一轮只展示最后一次有效正式预览,旧 ZIP 快照仍保留。正式概览必须是本轮最后一个工具操作;清理、检查和读图都在它之前完成。之后若继续调用任何工具,必须重新正式交付;完成正式交付后直接输出最终说明。最终正式工程的预览调用 `render_kicad_module_preview({schematicPath: 正式原理图路径, projectDir: 正式project目录, delivery: true})`,可复用同版本渲染缓存,在本轮正常完成后,于最终回复后的轮次尾部显示原理图概览、页面切换、缩放和工程 ZIP 下载卡片;执行期间仅保存工具结果,不立即展示卡片。中间布局检查不传 delivery。projectDir 必须同时包含根图、子页和工程所需本地库;ZIP 收集该目录内原理图、工程配置、符号、封装、库表及3D模型,不包含脚本、来源、预览或工作区外的库。交付返回 deliveryStatus:blocked 或 overviewError 时不得宣称正式交付成功;先依据 footprintCheck/error 修正缺库或服务问题,保留已有工程和预览,不为补库重画未变的电路。卡片不提供重新生成、详情或自动放置按钮。概览不代表电气验证通过。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
### KiCad Libraries License
|
|
2
|
+
|
|
3
|
+
The KiCad libraries are licensed under the [Creative Commons CC-BY-SA 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/legalcode), with the following exception:
|
|
4
|
+
|
|
5
|
+
---------
|
|
6
|
+
|
|
7
|
+
_To the extent that the creation of electronic designs that use 'Licensed Material' can be considered to be 'Adapted Material', then the copyright holder waives article 3 of the license with respect to these designs and any generated files which use data provided as part of the 'Licensed Material'._
|
|
8
|
+
|
|
9
|
+
---------
|
|
10
|
+
|
|
11
|
+
You can read more about KiCad library licensing [here](http://kicad.org/libraries/license).
|
|
12
|
+
|
|
13
|
+
**What does this mean?**
|
|
14
|
+
|
|
15
|
+
KiCad libraries are licensed in such a way to ensure free use of library data for commercial, closed, and non-commercial projects without restriction. KiCad does not wish to exert any control over designs produced using the library data, or force users to reveal proprietary information contained in their designs. Neither do we wish to force users to attribute the KiCad libraries _within their design_.
|
|
16
|
+
|
|
17
|
+
Use of the library data in a project does not (by itself) require that the design or any files generated from the design are licensed under the CC-BY-SA 4.0 License. You are free to use the library data in your own projects without the obligation to share your project files under this or any other license agreement.
|
|
18
|
+
|
|
19
|
+
However, if you wish to redistribute the KiCad libraries, or parts thereof (including in modified form) as a collection then the exception above does not apply. Redistributed library collections must be shared under the same license agreement. Under these circumstances, the libraries must also retain attribution information, including the license documents which are distributed with the library files.
|
|
20
|
+
|
|
21
|
+
----------------------
|
|
22
|
+
|
|
23
|
+
**Warranty**
|
|
24
|
+
|
|
25
|
+
The KiCad libraries are provided in the hope that they will be useful, but are provided without warranty of any kind, express or implied.
|
|
26
|
+
|
|
27
|
+
The KiCad libraries are compiled by the KiCad community - if you find an error in the library data, please help the community and contribute a fix!
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Passive crystal with verified external load capacitors; not an oscillator design rule. */
|
|
2
|
+
import { vertical, wire, point } from './geometry.mjs';
|
|
3
|
+
export function place({ region, at: [x, y], parts, references: refs, nets, externalCrystalConfirmed }) {
|
|
4
|
+
if (externalCrystalConfirmed !== true) {
|
|
5
|
+
throw new Error('Verify an external passive crystal is used and requires these load capacitors');
|
|
6
|
+
}
|
|
7
|
+
const { pins, ...symbol } = parts.crystal;
|
|
8
|
+
if (!pins.left || !pins.right || parts.capacitors.length !== 2 || refs.capacitors.length !== 2) {
|
|
9
|
+
throw new Error('Provide two crystal terminal roles and two capacitors');
|
|
10
|
+
}
|
|
11
|
+
region.addSymbol({ ...symbol, reference: refs.crystal, at: point([x + 38.1, y + 15.24]),
|
|
12
|
+
referenceAt: point([x + 38.1, y + 2.54]), valueAt: point([x + 38.1, y + 6.35]) });
|
|
13
|
+
const left = point(region.pin(refs.crystal, pins.left)), right = point(region.pin(refs.crystal, pins.right));
|
|
14
|
+
if (left[0] >= right[0] || left[1] !== right[1]) {
|
|
15
|
+
throw new Error('Orient crystal terminals horizontally without exchanging physical roles');
|
|
16
|
+
}
|
|
17
|
+
const nodes = [[left[0] - 12.7, left[1]], [right[0] + 12.7, right[1]]];
|
|
18
|
+
const caps = parts.capacitors.map((part, i) => vertical(region, part, refs.capacitors[i],
|
|
19
|
+
point([nodes[i][0], left[1] + 15.24]), 'top', 'bottom'));
|
|
20
|
+
if (caps.some(c => c.top[1] <= left[1])) throw new Error('Increase crystal load-capacitor spacing');
|
|
21
|
+
return {
|
|
22
|
+
reservedBounds: { origin: [x, y], size: [81.28, 58.42] },
|
|
23
|
+
ports: { left: { reference: refs.crystal, pin: pins.left },
|
|
24
|
+
right: { reference: refs.crystal, pin: pins.right } },
|
|
25
|
+
connect() {
|
|
26
|
+
[left, right].forEach((p, i) => {
|
|
27
|
+
const node = nodes[i], c = caps[i], ground = [c.bottom[0], c.bottom[1] + 7.62];
|
|
28
|
+
wire(region, p, node); wire(region, node, c.top); region.marker('junction', node);
|
|
29
|
+
region.label(i === 0 ? nets.left : nets.right, node);
|
|
30
|
+
wire(region, c.bottom, ground); region.label(nets.ground, ground);
|
|
31
|
+
});
|
|
32
|
+
for (const pin of pins.caseGround ?? []) region.label(nets.ground, point(region.pin(refs.crystal, pin)));
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|