EasyEDA PCB 快速设计Skills
开源仓库 :github.com/sakurajh/easyeda-pcb-workflow
个人博客 :sakura.work.gd
EasyEDA PCB 快速设计工作流
目标 :从需求到 EDA 画完,全程照着流程走,不需要反复试错。
前提 :已通过
easyeda-apiskill 连接 EDA 桥接服务。
一、设计前必读:核心概念
1.1 坐标系统
| 项目 | 值 | 说明 |
|---|---|---|
| PCB 单位 | 1 mil = 0.0254 mm | mm(v) = v × 39.37 |
| 原点 | 左下角 | x 向右,y 向上 |
| 常用换算 | 1mm = 39.37mil | 86mm = 3385mil |
| 文字字号 | 需 ×1.25 补偿 | EDA 字形只占字号 0.7 倍 |
⚠️ 最容易搞错的 :设计稿用 mm,EDA API 用 mil。所有坐标必须 mm(v) = Math.round(v * 39.37 * 100) / 100。
1.2 层 ID 映射(六层板)
| 视觉层 | 铜层 ID | 阻焊 ID | 丝印 ID | 用途 |
|---|---|---|---|---|
| L1 顶层 | 1 (TOP) | 5 (TOP_SOLDER_MASK) | 3 | 金色图案 = 铜箔 + 阻焊开窗 |
| L2 内层1 | 15 (INNER_1) | --- | --- | 装饰/地平面 |
| L3 内层2 | 16 (INNER_2) | --- | --- | 装饰/星点 |
| L4 内层3 | 17 (INNER_3) | --- | --- | 装饰/网格 |
| L5 内层4 | 18 (INNER_4) | --- | --- | 装饰/辐射纹 |
| L6 底层 | 2 (BOTTOM) | 6 (BOTTOM_SOLDER_MASK) | 4 | 金色图案(⚠️需镜像修正) |
| 板框 | 11 (BOARD_OUTLINE) | --- | --- | 板外形 + 挂孔/挖槽 |
| 锡膏 | 7/8 | --- | --- | 钢网(装饰板不需要) |
⚠️ 关键:金色效果 = 铜箔层(1/2)+ 同图案的阻焊开窗层(5/6)。沉金工艺在开窗处露出金色。
1.3 图元 API 速查
| 图元 | API | 参数要点 |
|---|---|---|
| 填充矩形 | pcb_PrimitiveFill.create(layer, poly('R', x, yTop, w, h, 0, r), '', 0, 6, false) |
y 是顶边,不是底边! |
| 填充圆 | pcb_PrimitiveFill.create(layer, poly('CIRCLE', x, y, r), '', 0, 6, false) |
|
| 折线 | pcb_PrimitivePolyline.create('', layer, poly([x1,y1,'L',...pts]), w, false) |
不自动闭合,需手动回到起点 |
| 文字 | pcb_PrimitiveString.create(layer, x, y, text, 'default', size, lw, 5, rot, false, 0, false, false) |
size ×1.25 补偿 |
| 图像 | pcb_PrimitiveImage.create(x, y, cp, layer, w, h, 0, false, false) |
x,y 是左下角 |
| 过孔 | pcb_PrimitiveVia.create('', x, y, drill, pad, 0) |
参数从简,多了会报错 |
| 焊盘 | pcb_PrimitivePad.create(...) |
用于元件焊盘 |
二、标准流程(照着走)
Step 1:环境检查
bash
# 扫描桥接端口
for port in $(seq 49620 49629); do
resp=$(curl -s --max-time 1 http://127.0.0.1:$port/health 2>/dev/null)
if echo "$resp" | grep -q '"easyeda-bridge"'; then echo "PORT=$port"; break; fi
done
# 确认 EDA 已连接
curl -s http://127.0.0.1:49620/health
# 确认文档类型(必须是 3 = PCB)
curl -s -X POST http://127.0.0.1:49620/execute \
-H "Content-Type: application/json" \
-d '{"code":"const d = await eda.dmt_SelectControl.getCurrentDocumentInfo(); return JSON.stringify(d);"}'
Step 2:建工程 + PCB
javascript
// 创建工程
const projUuid = await eda.dmt_Project.createProject('名称', '代号', undefined, undefined, '描述');
await eda.dmt_Project.openProject(projUuid);
// 创建 PCB
const pcbUuid = await eda.dmt_Pcb.createPcb();
await eda.dmt_EditorControl.openDocument(pcbUuid);
⚠️ 坑 :createProject + openProject + createPcb 合计耗时 >30s,单次 /execute 会超时。用 90s timeout。
Step 3:画板框(必须严格闭合)
javascript
// 圆角矩形:100点,首尾必须重合(误差 <0.001mm)
const pts = []; // 四角圆弧采样
pts.push([pts[0][0], pts[0][1]]); // ← 这行不能少!
const mk = (f) => eda.pcb_MathPolygon.createPolygon([f[0], f[1], 'L', ...f.slice(2)]);
await eda.pcb_PrimitivePolyline.create('', 11, mk(flat(pts)), mm(0.15), false);
// 挂孔/挖槽:同样闭合
await eda.pcb_PrimitivePolyline.create('', 11, mk(flat(hole)), mm(0.15), false);
⚠️ 最大的坑:板框不闭合 → 3D 里板子变成挂孔那个小圆饼!EDA 把闭合的孔当成板外形。
Step 4:逐层绘制
javascript
// 分批写入,每批 ≤40 个
async function pushItems(items, layer) {
for (let i = 0; i < items.length; i += 40) {
const batch = items.slice(i, i + 40);
// ... 用 pcb_PrimitiveFill/Polyline/String/Image 逐个创建
}
}
// 顺序:内层(15-18) → 顶层铜箔(1) → 底层铜箔(2) → 阻焊(5/6)
Step 5:图像描摹(校徽/签名/Logo)
javascript
// 1. 图片预处理(Python PIL)
// - 灰度 → 二值化(阈值150) → 裁白边 → 缩放480px
// - 笔迹=黑(0),背景=白(255) ← 极性别搞反!
// 2. 传入 EDA 描摹
const blob = await (await fetch('data:image/png;base64,' + b64)).blob();
const cp = await eda.pcb_MathPolygon.convertImageToComplexPolygon(
blob, 480, 480, 0.55, 0.35, 0.5, 1.5, true, false
);
// 3. 放置(x,y = 左下角)
await eda.pcb_PrimitiveImage.create(x, y, cp, layer, w, h, 0, false, false);
// 铜箔(1) + 阻焊开窗(5) 各放一次 = 金色效果
⚠️ 描摹参数 (0.6, 0.3, 0.45, 2.0) 会内部报错 ,用 (0.55, 0.35, 0.5, 1.5)。
⚠️ 图片极性:黑(0)=笔迹,白(255)=背景。反了会描出白底方块。
Step 6:过孔
javascript
await eda.pcb_PrimitiveVia.create('', x, y, mm(0.4), mm(0.9), 0); // drill, pad
// 别加多余参数(null),会报"参数不正确"
Step 7:保存 + 验证
javascript
await eda.pcb_Document.save();
// 统计各层图元
const all = await eda.pcb_PrimitiveFill.getAll(); // 不带 layer 参数
// ... 按 getState_Layer() 自行分类统计
三、避坑速查表(按出现频率排序)
🔴 致命错误
| # | 问题 | 症状 | 解决 |
|---|---|---|---|
| 1 | 板框不闭合 | 3D 里板子变成小圆饼 | 首尾点重合,误差 <0.001mm |
| 2 | 文档类型不对 | 所有 API 返回 null / "参数不正确" | 先 openDocument(pcbUuid) 确认 type=3 |
| 3 | 底层不镜像 | 背面校徽跑到板外 | L6/L6mask 的 x 用 3150 - x |
| 4 | 图片极性反 | 描出白底方块而非笔迹 | 笔迹=黑(0),背景=白(255) |
| 5 | getAll 带 layer 参数 | 返回 null | 用 getAll() 不带参数,自行 filter |
🟡 常见错误
| # | 问题 | 症状 | 解决 |
|---|---|---|---|
| 6 | 文字太小 | 比设计稿小 30% | size × 1.25 |
| 7 | 填充矩形 y 错位 | 矩形偏上/偏下 | 'R' 的 y 是顶边 |
| 8 | 折线不闭合 | 少一条边 | 手动加回到起点的线段 |
| 9 | 过孔创建失败 | 返回 null | 参数只传 6 个:(net, x, y, drill, pad, type) |
| 10 | 描摹参数错 | "Cannot read properties of null" | 用 (0.55, 0.35, 0.5, 1.5) |
| 11 | 'L' 模式填充失败 | Fill.create 返回 null | 'L' 只能用于 Polyline,不能用于 Fill |
| 12 | 任务超时 | 30s 未返回 | 复杂操作分批,每批 ≤40 个图元 |
| 13 | 重复图元 | 多次运行脚本叠加 | 每次先清旧再放新 |
| 14 | 丝印残留 | 旧文字不消失 | 清除 layer 3/4 的 String |
🟢 已知特性(非 bug)
| 现象 | 原因 | 处理 |
|---|---|---|
| 2D 里底层内容偏左 | 底层镜像显示 | 正常,3D 里正确 |
| 内层图案透过顶层可见 | 2D 视图多层叠加 | 正常,3D 里被阻焊盖住 |
| DRC 报孤岛铜/无网络 | 纯装饰板无电气网络 | 正常,可跳过 DRC |
| 阻焊开窗碰到板边 | 装饰板故意铺满 | 正常,不影响打样 |
四、加速技巧
4.1 批量写入模式
javascript
// 把一批图元序列化后一次 /execute 发过去
const data = JSON.stringify(items); // ≤40个
const code = `const items = ${data}; /* 逐个创建 */ return ok;`;
await exec(code, 60000);
4.2 几何生成模板
javascript
// 基础图元(设计层用)
const stroke = (pts, w) => ({ t: 'stroke', pts, w });
const fillRect = (x, y, w, h, r=0) => ({ t: 'fillRect', x, y, w, h, round: r });
const fillCircle = (cx, cy, r) => ({ t: 'fillCircle', cx, cy, r });
const text = (x, y, s, size, rot=0, center=true) => ({ t: 'text', x, y, s, size, rot, center });
// 圆弧采样
function arcPts(cx, cy, r, a0, a1, steps=48) {
const pts = [];
for (let i = 0; i <= steps; i++) {
const a = (a0 + (a1-a0)*(i/steps)) * Math.PI/180;
pts.push([cx + r*Math.cos(a), cy + r*Math.sin(a)]);
}
return pts;
}
const ring = (cx, cy, r, w) => stroke(arcPts(cx, cy, r, 0, 360, 64), w);
4.3 贴片元件封装(建筑/装饰用)
javascript
function smd(cx, cy, w, h) {
const pad = Math.min(w*0.35, 1.2), gap = w*0.12;
return [
fillRect(cx-w/2, cy-h/2, pad, h, 0.05), // 左焊盘
fillRect(cx+w/2-pad, cy-h/2, pad, h, 0.05), // 右焊盘
fillRect(cx-w/2+pad+gap, cy-h/2+1, w-2*pad-2*gap, h-2, 0.05), // 本体
];
}
4.4 验证抓图
javascript
// 抓 EDA 画布
const blob = await eda.dmt_EditorControl.getCurrentRenderedAreaImage();
// → base64 → PNG 文件 → ViewImage 查看
4.5 自动化脚本结构
project.mjs → 建工程+PCB(单次,90s timeout)
outline.mjs → 板框+挂孔
layers.mjs → 逐层绘制(分批)
images.mjs → 校徽/签名描摹放置
vias.mjs → 过孔
save.mjs → 保存+统计
grab.mjs → 抓画布验证
每个脚本开头都先 openDocument(pcbUuid) 确认文档类型!
五、工艺效果速查
| 想要的效果 | 做法 |
|---|---|
| 金色图案(绿油底) | 铜箔层画图案 + 阻焊层开窗同样图案 + 沉金 ENIG |
| 黑金卡 | 黑色阻焊 + 沉金 |
| 红金卡 | 红色阻焊 + 沉金 |
| 暗纹底色 | 铜层画网格/纹理但不开窗,被阻焊压住 |
| 白色文字 | 丝印层(3/4)放文字 |
| 立体浮雕 | 多层铜箔叠不同图案 + 大量装饰过孔 |
| NFC 天线 | 底层画 4 圈螺旋线圈,天线区域不铺铜 |
六、打样参数模板
| 项目 | 装饰卡 | 功能板 | 六层免费券 |
|---|---|---|---|
| 尺寸 | 86×54mm | 自定义 | ≤100×100mm |
| 层数 | 2 | 2 | 6 |
| 板厚 | 1.6mm / 1.2mm | 1.6mm | 1.6mm |
| 阻焊 | 红/黑 | 绿 | 绿 |
| 表面处理 | 沉金 ENIG | HASL/沉金 | 沉金 |
| 数量 | 5 | 5 | 5 |
| DRC | 可跳过 | 需通过 | 可跳过 |
七、调试工具箱
检查文档状态
javascript
const p = await eda.dmt_Project.getCurrentProjectInfo();
const d = await eda.dmt_SelectControl.getCurrentDocumentInfo();
// d.documentType 必须是 3(PCB)
检查图元分布
javascript
const imgs = await eda.pcb_PrimitiveImage.getAll(); // 不带参数!
const lc = {};
for (const p of imgs) { const L = p.getState_Layer(); lc[L] = (lc[L]||0)+1; }
检查板框闭合
javascript
const src = prim.getState_Polygon().getSource();
const nums = src.filter(v => typeof v === 'number');
const closed = Math.abs(nums[0]-nums[nums.length-2]) < 0.01
&& Math.abs(nums[1]-nums[nums.length-1]) < 0.01;
清空重建
javascript
for (const cls of ['pcb_PrimitiveFill','pcb_PrimitivePolyline','pcb_PrimitiveString','pcb_PrimitiveImage']) {
const all = await eda[cls].getAll(); // 不带参数
if (all?.length) await eda[cls].delete(all.map(p => p.getState_PrimitiveId()));
}
开源地址
本工作流已开源,欢迎 Star!
- GitHub 仓库 :sakurajh/easyeda-pcb-workflow
- 博客全文:[sakura.work.gd