# Easyeda Draw Checking

> Schematic verification and quality checking for EasyEDA Pro. Invoke when verifying layout, checking for overlaps, validating designators, or running design rule checks.

- Skill: `kukucaicndy/easyeda-draw-checking` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add kukucaicndy/easyeda-draw-checking`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kukucaicndy/easyeda-draw-checking/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: kukucaicndy (https://skillmd.com/u/kukucaicndy)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kukucaicndy/easyeda-draw-checking

---


# 原理图检查

布局验证、重叠检测、位号检查、DRC 的完整指南。

> **⚠️ 重要**：所有检查必须通过 CLI 执行，禁止使用 curl 直接调用 API。

---

## 0. 快捷入口：CLI 一键检查

按绘制阶段使用不同的检查命令：

```bash
# 摆放一个模块后（每个模块摆放完必须执行）
./scripts/draw_cli.py verify-placement

# 连线一个模块后（每个模块连线完必须执行）
./scripts/draw_cli.py verify-wiring

# 全部模块完成后（最终提交前必须执行）
./scripts/draw_cli.py check-all

# DRC 不通过时查看逐项详情
./scripts/draw_cli.py drc --show-ui
```

---

## 1. DRC 检查（Design Rule Check）

### 1.0 DRC API 限制（重要）

> **⚠️ EasyEDA SCH_Drc API 只返回聚合统计**：
> ```javascript
> // check(true, false, true) 返回的是：
> [{ type: "fatalError", count: 1 }, { type: "warn", count: 11 }]
> ```
> **不返回每个错误的具体信息**（器件名、引脚号、错误描述等）。
>
> **查看逐项详细错误的唯一方式**：
> ```bash
> ./scripts/draw_cli.py drc --show-ui
> ```
> 这会呼出 EasyEDA 底部的 DRC 面板，面板中可看到每个错误的器件、引脚、具体描述。

### 1.1 基础 DRC

```javascript
// 仅知道是否通过
const passed = await eda.sch_Drc.check(true, false, false);
// passed: true = 通过, false = 有错误

// 获取详细错误统计
const errors = await eda.sch_Drc.check(true, false, true);
// 返回数组：[{ type: "fatalError"|"warn", count: N }]
```

### 1.2 DRC 问题排查流程

```
DRC 不通过
   │
   ├─ fatalError（致命错误）
   │   ├─ 未连接引脚 → 补全连线 或 添加 NC 标记
   │   ├─ 网络短路 → 检查同一网络是否有不该连接的引脚
   │   └─ 缺失网络标签 → 添加 NetFlag / NetPort
   │
   └─ warn（警告）
       ├─ 空网络导线 → 为导线设置网络名
       ├─ 未使用引脚 → 确认是否需要 NC 标记
       └─ 去耦电容缺失 → 在 IC 电源引脚附近补充
```

### 1.3 使用 easyeda-draw 函数

```javascript
import utils from './scripts/draw-utils.js';

// 运行 DRC 详细检查
const result = await utils.runDrc();
// { passed: boolean, fatalErrors: number, warnings: number, details: Array }
```

---

## 2. 重复标签检测

### 2.1 检测原理

遍历所有 NetFlag/NetPort 图元，按坐标分组，找出同一位置放置多次的图元。

### 2.2 使用函数

```javascript
import utils from './scripts/draw-utils.js';

// 检测重复标签
const result = await utils.detectDuplicateLabels();
// { duplicates: [{ position, items: [{id, net, type}] }] }

// 自动删除重复标签（每组保留第一个）
const cleanup = await utils.cleanupDuplicateLabels();
// { deleted: N, details: [...] }
```

### 2.3 手动检测

```javascript
const allComps = await eda.sch_PrimitiveComponent.getAll();
const posMap = new Map();

for (const c of allComps || []) {
  // 只检查 NetFlag/NetPort (designator 为空或 "?")
  if (c.designator && c.designator !== '?') continue;
  const key = `${c.x},${c.y}`;
  if (!posMap.has(key)) posMap.set(key, []);
  posMap.get(key).push({
    id: c.primitiveId,
    type: c.net ? 'NetFlag' : 'NetPort',
    net: c.net,
    rotation: c.rotation,
  });
}

const duplicates = [];
for (const [pos, items] of posMap) {
  if (items.length > 1) duplicates.push({ position: pos, items });
}
```

---

## 3. 导线网络名完整性检查

### 3.1 检测原理

遍历所有导线，检查网络名是否为空。

### 3.2 使用函数

```javascript
import utils from './scripts/draw-utils.js';

// 检测无网络名的导线
const result = await utils.checkWireNets();
// { total: N, noNet: M, wiresNoNet: [{id, line}] }
```

### 3.3 修复导线网络名

```javascript
// 逐个修复：用 getPrimitiveByPrimitiveId 获取导线 → toAsync → setState_Net → done
const w = await eda.sch_Primitive.getPrimitiveByPrimitiveId(wireId);
const aw = w.toAsync();
aw.setState_Net("NET_NAME");
aw.done();
```

---

## 4. 未连接引脚检测

### 4.1 检测原理

对每个真实器件（有设计位号的），检查各引脚是否被导线可达或连接了网络标签。

**改进点（与旧版本差异）：**

| 特性 | 旧版本 | 新版本 |
|------|--------|--------|
| 4PIN 轻触开关 | 所有引脚都检查 | PIN1↔PIN2 内部短接，PIN3↔PIN4 内部短接，只接一对即可 |
| NC 标记 | 不识别 | 有 NC 标记的引脚不算未连接 |
| 分级 | 全部报 error | 区分 error (应连接) 和 warning (4PIN 冗余引脚，正常) |

### 4.2 使用 CLI

```bash
./scripts/draw_cli.py check-unconnected
# 返回: { errorCount, warningCount, errors: [...], warnings: [...] }
# errors: 应连接但未连接的引脚
# warnings: 4PIN 开关内部短接的冗余引脚 (正常)
```

### 4.3 输出解读

- `errorCount > 0` → 有引脚应连接但未连接，必须修复
- `warningCount > 0` 且 `errorCount = 0` → 4PIN 开关冗余引脚，正常无需修复
- 4PIN 开关判断: 器件有 4 个引脚且符号名包含 SWITCH/轻触/4PIN/SMD_4

---

## 4.5 短路检测（NEW）

### 原理

用并查集 (union-find) 合并物理上相连的连接点，如果同一个连通分量中有 2+ 个不同网络名 → 短路。

### 使用 CLI

```bash
./scripts/draw_cli.py check-shorts
# 返回: { shortCount, shorts: [{nets, points, wireCount, wireIds}], passed }
```

### 典型短路场景

- VCC 和 GND 通过导线直接连通
- ROW0 和 ROW1 通过同一连接点误连
- 两个不同信号通过 NetPort 标签放在同一坐标

## 4.7 冗余导线检测（NEW）

### 原理

检查同两个物理点之间是否有多条导线。冗余导线不会引起电气错误，但让原理图凌乱。

### 使用 CLI

```bash
./scripts/draw_cli.py check-redundant-wires
# 返回: { redundantCount, redundant: [{endpoints, count, wires}], passed }
```

---

## 5. 布局验证（重叠检测）

### 检测所有器件重叠

```javascript
import utils from './scripts/draw-utils.js';

const { overlaps } = await utils.checkOverlaps();
// overlaps: [{comp1, comp2}]
```

### 手动检测

```javascript
const compIds = await eda.sch_PrimitiveComponent.getAllPrimitiveId();
const bboxes = [];

for (const id of compIds) {
  const comps = await eda.sch_PrimitiveComponent.get([id]);
  if (!comps || comps.length === 0) continue;
  const c = comps[0];
  const pins = await eda.sch_PrimitiveComponent.getAllPinsByPrimitiveId(id);
  
  if (pins && pins.length > 0) {
    let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
    for (const p of pins) {
      minX = Math.min(minX, p.x);
      maxX = Math.max(maxX, p.x);
      minY = Math.min(minY, p.y);
      maxY = Math.max(maxY, p.y);
    }
    bboxes.push({
      id,
      designator: c.designator,
      name: c.name,
      bbox: [minX, minY, maxX, maxY],
      area: (maxX - minX) * (maxY - minY),
    });
  }
}

// 检查重叠
const overlaps = [];
for (let i = 0; i < bboxes.length; i++) {
  for (let j = i + 1; j < bboxes.length; j++) {
    const a = bboxes[i].bbox;
    const b = bboxes[j].bbox;
    if (a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1]) {
      overlaps.push({
        comp1: bboxes[i].designator,
        comp2: bboxes[j].designator,
      });
    }
  }
}
```

### 间距检查

```javascript
const minSpacing = 40; // 最小间距 40 单位 (0.4 inch)
const tooClose = [];

for (let i = 0; i < bboxes.length; i++) {
  for (let j = i + 1; j < bboxes.length; j++) {
    const a = bboxes[i].bbox;
    const b = bboxes[j].bbox;
    
    const ax = (a[0] + a[2]) / 2;
    const ay = (a[1] + a[3]) / 2;
    const bx = (b[0] + b[2]) / 2;
    const by = (b[1] + b[3]) / 2;
    const dist = Math.sqrt((ax - bx) ** 2 + (ay - by) ** 2);
    
    if (dist < minSpacing) {
      tooClose.push({
        comp1: bboxes[i].designator,
        comp2: bboxes[j].designator,
        distance: dist,
        minRequired: minSpacing,
      });
    }
  }
}
```

---

## 6. 位号检查

### 检查缺失或异常位号

```javascript
const compIds = await eda.sch_PrimitiveComponent.getAllPrimitiveId();
const issues = [];

for (const id of compIds) {
  const comps = await eda.sch_PrimitiveComponent.get([id]);
  if (!comps || comps.length === 0) continue;
  const c = comps[0];
  
  // 检查位号是否存在
  if (!c.designator) {
    issues.push({ id, type: "missing_designator", name: c.name });
    continue;
  }
  
  // 检查位号是否包含 ?（未分配）
  if (c.designator.includes("?")) {
    issues.push({ id, type: "unassigned_designator", designator: c.designator, name: c.name });
  }
  
  // 检查位号格式（字母+数字）
  if (!/^[A-Z]+\d+$/.test(c.designator)) {
    issues.push({ id, type: "invalid_format", designator: c.designator, name: c.name });
  }
}
```

### 位号重复检查

```javascript
const designatorMap = new Map();
const duplicates = [];

for (const id of compIds) {
  const comps = await eda.sch_PrimitiveComponent.get([id]);
  const c = comps[0];
  if (c.designator) {
    if (designatorMap.has(c.designator)) {
      duplicates.push({
        designator: c.designator,
        ids: [designatorMap.get(c.designator), id],
      });
    } else {
      designatorMap.set(c.designator, id);
    }
  }
}
```

---

## 7. 文本标注检查

### 检测重复文本

```javascript
const textIds = await eda.sch_PrimitiveText.getAllPrimitiveId();
const textMap = new Map();

for (const tid of textIds) {
  const t = await eda.sch_PrimitiveText.get([tid]);
  if (t && t[0]) {
    const key = `${t[0].x},${t[0].y}`;
    if (!textMap.has(key)) textMap.set(key, []);
    textMap.get(key).push({ id: tid, content: t[0].content, fontSize: t[0].fontSize });
  }
}

const duplicateTexts = [];
for (const [pos, items] of textMap) {
  if (items.length > 1) duplicateTexts.push({ position: pos, items });
}
```

### 检查文本位置是否合理

```javascript
// 检查文本是否与器件重叠
const textOverlaps = [];
for (const t of texts) {
  for (const b of bboxes) {
    if (t.x > b.bbox[0] && t.x < b.bbox[2] && t.y > b.bbox[1] && t.y < b.bbox[3]) {
      textOverlaps.push({
        text: t.content,
        component: b.designator,
      });
    }
  }
}
```

---

## 8. 导线穿过器件检测（NEW）

### 8.1 检测原理

遍历所有导线的每个线段，与所有真实器件的 BBox（边界框）做交叉检测：
- 若导线端点在器件 BBox 内部（排除 ±3 容差的引脚边缘）→ 报出
- 若线段与器件 BBox 相交，且交叉长度 > 5 单位 → 报出

这能发现像 R7、R8 被 ROW 网络导线直接穿过的问题。

### 8.2 使用 CLI

```bash
./scripts/draw_cli.py check-wire-through
```

### 8.3 使用函数

```javascript
import utils from './scripts/draw-utils.js';

const result = await utils.checkWireThroughComponent();
// { wireThroughCount: N, wiresThroughComponents: [...], summary: "PASS/FAIL" }
```

---

## 9. 完整检查流程（一键检查，7 步）

### 使用 CLI

```bash
./scripts/draw_cli.py check-all
```

### 7 步检查清单

| 步骤 | 检查项 | CLI 命令 | 说明 |
|------|--------|----------|------|
| 1 | 重复标签 | check-duplicates | 同一坐标多个 NetFlag/NetPort |
| 2 | 导线网络名 | check-wire-nets | 无网络名的导线 |
| 3 | 未连接引脚 | check-unconnected | 4PIN 开关感知 + NC 标记感知 |
| 4 | 短路检测 | check-shorts | 不同网络名导线连接在一起 |
| 5 | 冗余导线 | check-redundant-wires | 同两点间多条导线 |
| 6 | 导线穿器件 | check-wire-through | 导线穿过元器件本体 |
| 7 | DRC | drc | EasyEDA 内置设计规则检查 |

---

## 10. 检查清单

### 布局检查
- [ ] 无器件重叠
- [ ] 器件间距 ≥ 最小间距要求
- [ ] 功能区域分离清晰
- [ ] 文本标注不遮挡器件
- [ ] 导线不穿过器件本体（使用 check-wire-through）

### 位号检查
- [ ] 所有真实器件有位号
- [ ] 位号无 `?` 后缀
- [ ] 位号格式正确（字母+数字）
- [ ] 位号无重复
- [ ] NetFlag/NetPort 位号为 `?` 属正常（忽略）

### 网络检查
- [ ] 所有电源引脚有 NetFlag
- [ ] 所有信号有 NetPort 或导线连接
- [ ] 网络名无冲突
- [ ] 无重复标签（同一坐标多个）
- [ ] 导线的网络名已设置（无 `(no-net)` 导线）

### NC 检查
- [ ] 不使用的 IC 引脚已标记 NC
- [ ] NC 标记不遮挡器件或其他标注
- [ ] 4PIN 开关冗余引脚无需 NC 标记 (check-unconnected 自动识别)

### 电气检查
- [ ] 无短路 (check-shorts 通过)
- [ ] 无冗余导线 (check-redundant-wires 通过)
- [ ] 导线不穿过器件本体 (check-wire-through 通过)

### DRC 检查
- [ ] DRC 通过（无 fatal error）
- [ ] 警告项评估后确认可接受

### 文档检查
- [ ] 功能区域标注完整
- [ ] 信号连接说明清晰
- [ ] 版本信息/日期/设计者标注

