dsh-eda-mcp 1.0.1 → 1.0.2
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/CHANGELOG.md +29 -0
- package/docs/schematic-workflow.md +1 -1
- package/dsh-eda-bridge-extension-0.4.9.zip +0 -0
- package/jlc-extension/api-bridge.js +69 -19
- package/jlc-extension/extension.json +1 -1
- package/lib/bridge.mjs +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/src/bridge/index.ts +2 -1
- package/src/skills-content.ts +1 -1
- package/dsh-eda-bridge-extension-0.4.6.zip +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.2 — 2026-10-20
|
|
4
|
+
|
|
5
|
+
### 覆铜调查结案(扩展 0.4.7 / 0.4.8 真机)
|
|
6
|
+
|
|
7
|
+
结论:**这台构建(EDA 3.2.186 / pro-api 0.3.18.0bc12bb0)无法通过 API 创建铜面图元**。不是参数问题,证据链如下。
|
|
8
|
+
|
|
9
|
+
| 尝试 | 结果 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `pcb_PrimitivePour.create` 静态入口 × 8 种组合(单/复杂多边形、原始源码数组、空网络、层 1/2/字符串、全参数显式、100×100 与 300×300 mil) | 全部 `无法创建覆铜边框图元` |
|
|
12
|
+
| 实例入口 `new IPCB_PrimitivePour(...).create()`(0.4.7 新增,验证构造器在运行时可取) | 同样报错 |
|
|
13
|
+
| 填充方式 `solid`/`45grid`/`90grid`/`0`/`1`/`2`/`SOLID`/`GRID45`/`45GRID` | 全部报错 |
|
|
14
|
+
| `pcb_PrimitiveFill.create`(文档说明它同样带 `net` 参数)× 4 种(对象/原始数组/复杂源码/显式 fillMode) | 全部 `无法创建填充图元` |
|
|
15
|
+
| **控制组**:同一多边形管线写丝印折线 `pcb_PrimitivePolyline` / `pcb_draw_polyline` | 也写不出来(`getAll` 0 条),而 `pcb_get_polylines` 内部路径能读到既有板框 1 条 |
|
|
16
|
+
|
|
17
|
+
外部资料也到此为止:`prodocs` 正文是前端渲染(HTML 只有导航),官方配方给的流程就是我们实现的那条(`createPolygon`/`createComplexPolygon` → pour → `rebuildCopperRegion`),搜 "无法创建覆铜边框图元" 无任何社区解法,应用 bundle 内也搜不到该字符串。
|
|
18
|
+
|
|
19
|
+
**因此撤掉 `pcb_create_copper_fill`**:它在这台构建上必然失败,留着就是给调用方挖坑。白名单里的 `pcb_MathPolygon.createComplexPolygon` 与 `pcb_PrimitiveFill.*` 保留(换构建可能有效),但不再包装成工具。
|
|
20
|
+
|
|
21
|
+
**正解**:覆铜在界面手工铺(GND → 顶层+底层 → 重新铺铜);`eda_design_audit` 会持续把缺覆铜记成 `copper-pour` warn,`eda_delivery_checklist` 也会带上它。
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### 通知堆叠 / 粘连(扩展 0.4.9)
|
|
25
|
+
|
|
26
|
+
屏幕实测:三条提示叠在画布上("未连接…点击右侧按钮"、"扩展已加载 v0.4.8"、"已连接;端口 39009"),且"未连接"那条永不消失。
|
|
27
|
+
|
|
28
|
+
- 根因:`showToastMessage` 的 `timer: 0` 语义是**永不自动隐藏**,而 `SYS_Message` **没有任何隐藏或替换 API**;连接成功时也没有人撤掉它。
|
|
29
|
+
- 修法:未连接提示改成 `showFollowMouseTip(tip, 8000)`,并在 WebSocket `onopen` 里 `removeFollowMouseTip` 显式撤掉;`showToast` 一律传有限时长(默认 4 s);文案改为"菜单 DSH MCP → 连接 DSH MCP 服务器"(跟随提示没有按钮)。
|
|
30
|
+
- 线协议 **0.4.8 → 0.4.9**(纯 UI 层)。
|
|
31
|
+
|
|
3
32
|
## 1.0.1 — 2026-10-20
|
|
4
33
|
|
|
5
34
|
1.0.0 的真机回归修掉 4 个 bug(全部由"跑一遍"发现,不是靠读代码):
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
| eda_launch 带 project 没打开工程 | 命令行路径被应用忽略 | 用 eda_open_project({ name }) 打开 |
|
|
147
147
|
| 关应用后改动丢失 | eda_close 走了强制路径 | 关前先保存;看返回的 forced |
|
|
148
148
|
| run_code 读大 JSON 被截断 | 宿主 read 有单行上限 | 用 node:fs 读盘或分片读 |
|
|
149
|
-
|
|
|
149
|
+
| 覆铜/填充一律被拒(无法创建覆铜边框图元 / 无法创建填充图元) | 应用侧限制:静态+实例入口、单/复杂多边形、原始源码数组、6 种填充枚举、全参数组合全部被拒;同一多边形管线在本构建上连丝印折线都写不出来 | 不要在 API 层反复试:覆铜请在界面手工铺(GND、顶层+底层);design_audit 持续记 copper-pour warn |
|
|
150
150
|
| pcb_draw_mounting_hole 报“无法创建焊盘图元” | pad 形状必填,且槽孔只能用 ELLIPSE | 已内建:圆孔 ELLIPSE(d,d),槽孔 ELLIPSE(长,宽)+hole[SLOT,宽,长];OBLONG/RECTANGLE 焊盘会被拒 |
|
|
151
151
|
| pcb_PrimitiveString.getAll / pcb_PrimitivePad.getAll 返回 0 | 这两个 API 按层查询 | 传 layer:丝印 3/4,多层焊盘 12;不传参恒为 0 |
|
|
152
152
|
| 按坐标算的越界没被发现 | 图框/原点假设与真实页面不一致 | 用 eda_get_page_info 的真实值 + eda_capture 导 PNG 看实际版面 |
|
|
Binary file
|
|
@@ -212,6 +212,9 @@ function dshEdaMcpStatus() {
|
|
|
212
212
|
'pcb_PrimitivePad.create',
|
|
213
213
|
'pcb_PrimitivePad.getAll',
|
|
214
214
|
'pcb_PrimitivePad.delete',
|
|
215
|
+
'pcb_PrimitiveFill.create',
|
|
216
|
+
'pcb_PrimitiveFill.getAll',
|
|
217
|
+
'pcb_PrimitiveFill.delete',
|
|
215
218
|
'pcb_PrimitivePour.create',
|
|
216
219
|
'pcb_PrimitivePour.modify',
|
|
217
220
|
'pcb_PrimitivePour.delete',
|
|
@@ -459,21 +462,54 @@ function dshEdaMcpStatus() {
|
|
|
459
462
|
: undefined;
|
|
460
463
|
if (!pourPolygon) pourPolygon = eda.pcb_MathPolygon.createPolygon(pourSource);
|
|
461
464
|
if (!pourPolygon) throw new Error('dsh.internal.pourCreate: invalid polygon source ' + JSON.stringify(pourSource));
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
465
|
+
// Route 1: the documented static factory.
|
|
466
|
+
var pourRoutes = [];
|
|
467
|
+
var pour = null;
|
|
468
|
+
var usedRoute = 'static';
|
|
469
|
+
try {
|
|
470
|
+
pour = await eda.pcb_PrimitivePour.create(
|
|
471
|
+
optional(pourArgs[0]), optional(pourArgs[1]), pourPolygon, optional(pourArgs[3]), optional(pourArgs[4]),
|
|
472
|
+
optional(pourArgs[5]), optional(pourArgs[6]), optional(pourArgs[7]), optional(pourArgs[8])
|
|
473
|
+
);
|
|
474
|
+
} catch (staticError) {
|
|
475
|
+
pourRoutes.push('static: ' + (staticError && staticError.message ? staticError.message : String(staticError)));
|
|
476
|
+
}
|
|
477
|
+
// Route 2: the instance entry point (@internal in the typings, but the class
|
|
478
|
+
// constructor is exported at runtime on some builds).
|
|
466
479
|
if (!pour) {
|
|
467
|
-
|
|
480
|
+
var PourCtor = null;
|
|
481
|
+
try {
|
|
482
|
+
if (typeof eda.IPCB_PrimitivePour === 'function') PourCtor = eda.IPCB_PrimitivePour;
|
|
483
|
+
else if (eda.pcb_PrimitivePour && typeof eda.pcb_PrimitivePour.constructor === 'function' && eda.pcb_PrimitivePour.constructor !== Object) PourCtor = eda.pcb_PrimitivePour.constructor;
|
|
484
|
+
} catch (ctorProbeError) { PourCtor = null; }
|
|
485
|
+
if (PourCtor !== null) {
|
|
486
|
+
usedRoute = 'instance';
|
|
487
|
+
try {
|
|
488
|
+
var pourInstance = new PourCtor(
|
|
489
|
+
optional(pourArgs[0]), optional(pourArgs[1]), pourPolygon, optional(pourArgs[3]), optional(pourArgs[4]),
|
|
490
|
+
optional(pourArgs[5]), optional(pourArgs[6]), optional(pourArgs[7]), optional(pourArgs[8])
|
|
491
|
+
);
|
|
492
|
+
pour = await pourInstance.create();
|
|
493
|
+
} catch (instanceError) {
|
|
494
|
+
pourRoutes.push('instance: ' + (instanceError && instanceError.message ? instanceError.message : String(instanceError)));
|
|
495
|
+
usedRoute = 'none';
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
pourRoutes.push('instance: no exported constructor');
|
|
499
|
+
usedRoute = 'none';
|
|
500
|
+
}
|
|
468
501
|
}
|
|
469
|
-
|
|
470
|
-
|
|
502
|
+
if (!pour) {
|
|
503
|
+
throw new Error('dsh.internal.pourCreate: every route refused (' + pourRoutes.join(' | ') + ')');
|
|
504
|
+
}
|
|
505
|
+
// A pour border alone leaves no copper: rebuild so the fill (and the clearance
|
|
506
|
+
// against other nets) is actually computed.
|
|
471
507
|
var rebuilt = false;
|
|
472
508
|
if (pour && typeof pour.rebuildCopperRegion === 'function') {
|
|
473
509
|
try { await pour.rebuildCopperRegion(); rebuilt = true; } catch (error) { rebuilt = false; }
|
|
474
510
|
}
|
|
475
511
|
var pourJson = toJsonable(pour);
|
|
476
|
-
if (pourJson && typeof pourJson === 'object') pourJson.rebuilt = rebuilt;
|
|
512
|
+
if (pourJson && typeof pourJson === 'object') { pourJson.rebuilt = rebuilt; pourJson.route = usedRoute; }
|
|
477
513
|
return pourJson;
|
|
478
514
|
}
|
|
479
515
|
// Internal helper: long-running jobs (auto-routing / auto-layout) are started
|
|
@@ -579,6 +615,9 @@ function dshEdaMcpStatus() {
|
|
|
579
615
|
function () {
|
|
580
616
|
wsReady = true;
|
|
581
617
|
lastPongAt = Date.now();
|
|
618
|
+
// The 'not connected' tip is sticky by design while we are offline; once the
|
|
619
|
+
// socket is up it must go away, otherwise it sits on the canvas forever.
|
|
620
|
+
hideConnectTip();
|
|
582
621
|
sendObject({ type: 'hello', version: VERSION });
|
|
583
622
|
}
|
|
584
623
|
);
|
|
@@ -599,10 +638,11 @@ function dshEdaMcpStatus() {
|
|
|
599
638
|
registerWebSocket();
|
|
600
639
|
}
|
|
601
640
|
|
|
602
|
-
function showToast(message, type) {
|
|
641
|
+
function showToast(message, type, seconds) {
|
|
603
642
|
try {
|
|
604
643
|
if (typeof eda !== 'undefined' && eda.sys_Message && typeof eda.sys_Message.showToastMessage === 'function') {
|
|
605
|
-
|
|
644
|
+
// Always pass a finite timer: omitting it (or 0) leaves the toast on screen forever.
|
|
645
|
+
eda.sys_Message.showToastMessage(message, type || 'info', seconds || 4);
|
|
606
646
|
} else {
|
|
607
647
|
console.log('[dsh-eda-mcp] ' + message);
|
|
608
648
|
}
|
|
@@ -706,20 +746,30 @@ function dshEdaMcpStatus() {
|
|
|
706
746
|
}
|
|
707
747
|
}
|
|
708
748
|
|
|
749
|
+
// One removable tip instead of a stack of toasts: showToastMessage has no hide API
|
|
750
|
+
// (timer:0 meant "never"), so a stack of them used to pile up over the canvas and the
|
|
751
|
+
// '未连接' banner survived even after the connection was established (measured).
|
|
752
|
+
var CONNECT_TIP = 'DSH MCP 未连接:菜单 DSH MCP → 连接 DSH MCP 服务器(ws://127.0.0.1:39009/ws)';
|
|
753
|
+
|
|
754
|
+
function hideConnectTip() {
|
|
755
|
+
try {
|
|
756
|
+
if (typeof eda !== 'undefined' && eda.sys_Message && typeof eda.sys_Message.removeFollowMouseTip === 'function') {
|
|
757
|
+
eda.sys_Message.removeFollowMouseTip(CONNECT_TIP);
|
|
758
|
+
}
|
|
759
|
+
} catch (_) { /* ignore */ }
|
|
760
|
+
}
|
|
761
|
+
|
|
709
762
|
function showConnectToast() {
|
|
710
763
|
if (wsReady) return;
|
|
711
764
|
try {
|
|
712
765
|
if (typeof eda === 'undefined' || !eda.sys_Message) return;
|
|
713
|
-
eda.sys_Message.
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
'连接 DSH MCP',
|
|
719
|
-
"eda.sys_MessageBus.publishPublic('dsh-eda-mcp/connect', {})"
|
|
720
|
-
);
|
|
766
|
+
if (typeof eda.sys_Message.showFollowMouseTip === 'function') {
|
|
767
|
+
eda.sys_Message.showFollowMouseTip(CONNECT_TIP, 8000);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
eda.sys_Message.showToastMessage(CONNECT_TIP, 'warning', 8);
|
|
721
771
|
} catch (error) {
|
|
722
|
-
console.error('[dsh-eda-mcp] connect
|
|
772
|
+
console.error('[dsh-eda-mcp] connect tip failed:', error);
|
|
723
773
|
}
|
|
724
774
|
}
|
|
725
775
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"uuid": "d5e0a1b2c3f4456789abcdef01234567",
|
|
3
3
|
"name": "dsh-eda-bridge",
|
|
4
4
|
"displayName": "DSH EDA MCP Bridge",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.9",
|
|
6
6
|
"description": "Local WebSocket bridge used by the dsh-eda-mcp plugin to let DeepSeek Harness draw schematics and drive PCB operations in JLCEDA Pro through the official extension API.",
|
|
7
7
|
"entry": "./api-bridge.js",
|
|
8
8
|
"publisher": "dsh-eda-mcp",
|
package/lib/bridge.mjs
CHANGED
|
@@ -807,7 +807,7 @@ var WORKFLOW_GUIDE = WORKFLOW_SUMMARY + "\n\n" + [
|
|
|
807
807
|
].join("\n");
|
|
808
808
|
|
|
809
809
|
// src/bridge/index.ts
|
|
810
|
-
var VERSION = "0.4.
|
|
810
|
+
var VERSION = "0.4.9";
|
|
811
811
|
var WS_PORT = Number(process.env.DSH_EDA_MCP_WS_PORT ?? 39009);
|
|
812
812
|
var WS_HOST = "127.0.0.1";
|
|
813
813
|
var ALLOW_RAW_API = (process.env.DSH_EDA_MCP_ALLOW_RAW_API ?? "1") !== "0";
|
package/lib/index.js
CHANGED
|
@@ -179,7 +179,7 @@ var SKILL_PLAYBOOK_LINES = [
|
|
|
179
179
|
"| eda_launch \u5E26 project \u6CA1\u6253\u5F00\u5DE5\u7A0B | \u547D\u4EE4\u884C\u8DEF\u5F84\u88AB\u5E94\u7528\u5FFD\u7565 | \u7528 eda_open_project({ name }) \u6253\u5F00 |",
|
|
180
180
|
"| \u5173\u5E94\u7528\u540E\u6539\u52A8\u4E22\u5931 | eda_close \u8D70\u4E86\u5F3A\u5236\u8DEF\u5F84 | \u5173\u524D\u5148\u4FDD\u5B58\uFF1B\u770B\u8FD4\u56DE\u7684 forced |",
|
|
181
181
|
"| run_code \u8BFB\u5927 JSON \u88AB\u622A\u65AD | \u5BBF\u4E3B read \u6709\u5355\u884C\u4E0A\u9650 | \u7528 node:fs \u8BFB\u76D8\u6216\u5206\u7247\u8BFB |",
|
|
182
|
-
"| \u8986\u94DC\
|
|
182
|
+
"| \u8986\u94DC/\u586B\u5145\u4E00\u5F8B\u88AB\u62D2\uFF08\u65E0\u6CD5\u521B\u5EFA\u8986\u94DC\u8FB9\u6846\u56FE\u5143 / \u65E0\u6CD5\u521B\u5EFA\u586B\u5145\u56FE\u5143\uFF09 | \u5E94\u7528\u4FA7\u9650\u5236\uFF1A\u9759\u6001+\u5B9E\u4F8B\u5165\u53E3\u3001\u5355/\u590D\u6742\u591A\u8FB9\u5F62\u3001\u539F\u59CB\u6E90\u7801\u6570\u7EC4\u30016 \u79CD\u586B\u5145\u679A\u4E3E\u3001\u5168\u53C2\u6570\u7EC4\u5408\u5168\u90E8\u88AB\u62D2\uFF1B\u540C\u4E00\u591A\u8FB9\u5F62\u7BA1\u7EBF\u5728\u672C\u6784\u5EFA\u4E0A\u8FDE\u4E1D\u5370\u6298\u7EBF\u90FD\u5199\u4E0D\u51FA\u6765 | \u4E0D\u8981\u5728 API \u5C42\u53CD\u590D\u8BD5\uFF1A\u8986\u94DC\u8BF7\u5728\u754C\u9762\u624B\u5DE5\u94FA\uFF08GND\u3001\u9876\u5C42+\u5E95\u5C42\uFF09\uFF1Bdesign_audit \u6301\u7EED\u8BB0 copper-pour warn |",
|
|
183
183
|
"| pcb_draw_mounting_hole \u62A5\u201C\u65E0\u6CD5\u521B\u5EFA\u710A\u76D8\u56FE\u5143\u201D | pad \u5F62\u72B6\u5FC5\u586B\uFF0C\u4E14\u69FD\u5B54\u53EA\u80FD\u7528 ELLIPSE | \u5DF2\u5185\u5EFA\uFF1A\u5706\u5B54 ELLIPSE(d,d)\uFF0C\u69FD\u5B54 ELLIPSE(\u957F,\u5BBD)+hole[SLOT,\u5BBD,\u957F]\uFF1BOBLONG/RECTANGLE \u710A\u76D8\u4F1A\u88AB\u62D2 |",
|
|
184
184
|
"| pcb_PrimitiveString.getAll / pcb_PrimitivePad.getAll \u8FD4\u56DE 0 | \u8FD9\u4E24\u4E2A API \u6309\u5C42\u67E5\u8BE2 | \u4F20 layer\uFF1A\u4E1D\u5370 3/4\uFF0C\u591A\u5C42\u710A\u76D8 12\uFF1B\u4E0D\u4F20\u53C2\u6052\u4E3A 0 |",
|
|
185
185
|
"| \u6309\u5750\u6807\u7B97\u7684\u8D8A\u754C\u6CA1\u88AB\u53D1\u73B0 | \u56FE\u6846/\u539F\u70B9\u5047\u8BBE\u4E0E\u771F\u5B9E\u9875\u9762\u4E0D\u4E00\u81F4 | \u7528 eda_get_page_info \u7684\u771F\u5B9E\u503C + eda_capture \u5BFC PNG \u770B\u5B9E\u9645\u7248\u9762 |",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-eda-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "DeepSeek Harness (DSH) plugin — connects DSH to JLCEDA Pro (EasyEDA Pro) via a local MCP bridge and draws schematics through the JLCEDA extension API.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": {
|
package/src/bridge/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from './design-rules.ts'
|
|
22
22
|
import { WORKFLOW_GUIDE, WORKFLOW_SUMMARY } from './workflow-guide.ts'
|
|
23
23
|
|
|
24
|
-
const VERSION = '0.4.
|
|
24
|
+
const VERSION = '0.4.9'
|
|
25
25
|
const WS_PORT = Number(process.env.DSH_EDA_MCP_WS_PORT ?? 39009)
|
|
26
26
|
const WS_HOST = '127.0.0.1'
|
|
27
27
|
const ALLOW_RAW_API = (process.env.DSH_EDA_MCP_ALLOW_RAW_API ?? '1') !== '0'
|
|
@@ -3136,6 +3136,7 @@ async function executeTool(name, rawArgs, timeoutMs) {
|
|
|
3136
3136
|
case 'eda_pcb_delete_via':
|
|
3137
3137
|
return await callExtension('pcb_PrimitiveVia.delete', [String(args.primitiveId)], timeoutMs)
|
|
3138
3138
|
|
|
3139
|
+
|
|
3139
3140
|
case 'eda_pcb_create_pour': {
|
|
3140
3141
|
const layer = resolveRegionLayer(args.layer, 'eda_pcb_create_pour')
|
|
3141
3142
|
const points = requireFlatPoints(args.points, 'eda_pcb_create_pour')
|
package/src/skills-content.ts
CHANGED
|
@@ -153,7 +153,7 @@ export const SKILL_PLAYBOOK_LINES: readonly string[] = [
|
|
|
153
153
|
"| eda_launch 带 project 没打开工程 | 命令行路径被应用忽略 | 用 eda_open_project({ name }) 打开 |",
|
|
154
154
|
"| 关应用后改动丢失 | eda_close 走了强制路径 | 关前先保存;看返回的 forced |",
|
|
155
155
|
"| run_code 读大 JSON 被截断 | 宿主 read 有单行上限 | 用 node:fs 读盘或分片读 |",
|
|
156
|
-
"|
|
|
156
|
+
"| 覆铜/填充一律被拒(无法创建覆铜边框图元 / 无法创建填充图元) | 应用侧限制:静态+实例入口、单/复杂多边形、原始源码数组、6 种填充枚举、全参数组合全部被拒;同一多边形管线在本构建上连丝印折线都写不出来 | 不要在 API 层反复试:覆铜请在界面手工铺(GND、顶层+底层);design_audit 持续记 copper-pour warn |",
|
|
157
157
|
"| pcb_draw_mounting_hole 报“无法创建焊盘图元” | pad 形状必填,且槽孔只能用 ELLIPSE | 已内建:圆孔 ELLIPSE(d,d),槽孔 ELLIPSE(长,宽)+hole[SLOT,宽,长];OBLONG/RECTANGLE 焊盘会被拒 |",
|
|
158
158
|
"| pcb_PrimitiveString.getAll / pcb_PrimitivePad.getAll 返回 0 | 这两个 API 按层查询 | 传 layer:丝印 3/4,多层焊盘 12;不传参恒为 0 |",
|
|
159
159
|
"| 按坐标算的越界没被发现 | 图框/原点假设与真实页面不一致 | 用 eda_get_page_info 的真实值 + eda_capture 导 PNG 看实际版面 |",
|
|
Binary file
|