# Easyeda Draw

> Draw and layout schematics in EasyEDA Pro. Invoke when user asks to draw circuit schematics, place components, adjust layout, fix overlapping parts, or create schematic pages in EasyEDA.

- Skill: `kukucaicndy/easyeda-draw` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add kukucaicndy/easyeda-draw`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kukucaicndy/easyeda-draw/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

---


# EasyEDA 原理图绘制

在 EasyEDA Pro 中通过 API 绘制原理图。本 skill 为入口，详细子 skill 见下方目录。

## 使用方式

本 skill 支持两种使用模式：

1. **完整流程模式**——从头到尾绘制一张完整原理图。**必须采用多 Agent 编排方式**：主 Agent 做任务规划 → 每个模块召唤绘制子 Agent → 每个模块召唤检查子 Agent → 整改闭环。完整编排流程见 [orchestration/](orchestration/skill.md)；绘制细节按 [SOP 标准绘制步骤](sop/skill.md) 逐模块推进。
2. **任务模式**——选取合适的子技能完成单个特定任务（如"放一个器件""画一根导线""跑一次 DRC"）。直接用 CLI 命令或查阅对应子 skill 文档即可，无需走完整 SOP。

> **判断依据**：用户说"画原理图"/"绘制电路"→ 完整流程模式（必读 orchestration）；用户说"放个电阻"/"检查 DRC"/"画根线"→ 任务模式。

### 多 Agent 编排：网络标签传递

> **每个模块完成后，主 Agent 必须执行 `net-summary` 收集当前网络标签快照，并传递给下一个子 Agent。**
>
> 这样下一个子 Agent 知道：
> - 哪些网络已存在（如 ROW0、COL0、VCC、GND）
> - 这些网络在什么坐标
> - 应该在哪些位置对接同名网络标签
>
> 避免子 Agent 盲目创建重复标签或放错坐标。


---

## 第一步：环境准备（必须先做）

> **⚠️ 在执行任何绘制命令之前，必须先完成以下两步，确认 Bridge Server 运行且 EDA 已连接。**

### 1. 启动 Bridge Server

Bridge Server 是 AI 与 EasyEDA 之间的通信桥梁，由 [easyeda-api](../../easyeda-api/SKILL.md) skill 提供。

```bash
# 检查 bridge 是否已在运行 (扫描端口 49620-49629)
for port in $(seq 49620 49629); do
  resp=$(curl -s http://localhost:$port/health 2>/dev/null)
  if echo "$resp" | grep -q '"easyeda-bridge"'; then
    echo "Bridge already running on port $port"
    break
  fi
done

# 若未运行，则启动 (后台运行，不阻塞)
node ../../easyeda-api/scripts/bridge-server.mjs &
sleep 2
```

同时确保 EasyEDA Pro 中已安装并加载 `run-api-gateway.eext` 扩展（下载：https://ext.lceda.cn/item/oshwhub/run-api-gateway ）。

### 2. 检查连通性

```bash
# 一键检查 bridge + EDA 连通性 (CLI 自动扫描端口 49620-49629)
./scripts/draw_cli.py health
```

期望输出：
```json
{"ok": true, "summary": "Bridge OK (port 49620), EDA connected", "bridge": true, "port": 49620, "edaConnected": true}
```

- `bridge: false` → Bridge 未启动，回到第 1 步
- `edaConnected: false` → EasyEDA 扩展未加载，检查 `run-api-gateway.eext`
- 多窗口场景：输出中 `windows` 列出所有已连接的 EDA 窗口，`activeWindow` 为当前活动窗口

> 端口也可通过环境变量强制指定：`EDA_BRIDGE_PORT=49622 ./scripts/draw_cli.py health`

---

## 子 Skill 目录

| 子 Skill | 路径 | 用途 |
|---------|------|------|
| **多 Agent 编排** | [orchestration/](orchestration/skill.md) | 完整流程模式必读：任务规划 → 绘制子 Agent → 检查子 Agent → 整改闭环 |
| **绘制规范** | [rules/](rules/skill.md) | 原理图设计规范、信号流向、布局约定、命名标准 |
| **标准绘制步骤** | [sop/](sop/skill.md) | 从主控到外围的标准绘制流程，含 NC 标记和检查 SOP |
| **模块子流程** | [sop/subflow.md](sop/subflow.md) | 每个外围模块的详细绘制子流程（P0 布局规划→P8 策略检查） |
| **器件摆放** | [placement/](placement/skill.md) | 元器件放置、位号分配、旋转镜像、间距控制 |
| **自定义符号** | [symbol/](symbol/skill.md) | 创建自定义符号/器件，系统库找不到时使用 |
| **连线** | [wiring/](wiring/skill.md) | 导线绘制、网络标签、NC 标记、电源/地符号 |
| **检查** | [checking/](checking/skill.md) | 布局验证、DRC、重复标签、导线网络名、未连接引脚检查 |
| **绘制后优化策略** | [strategies/](strategies/skill.md) | 同名合并、NetFlag对齐、冗余合并、NC标注、信号规范、网络名、重复标签、DRC |

## 核心规则（MUST FOLLOW）

### 0. 完整流程模式必须多 Agent 编排

> **⚠️ 当用户要求"画完整原理图"时，禁止主 Agent 亲自下场逐个 `place`/`wire`。**
>
> 正确方式：
> 1. 主 Agent 做 Phase A 任务规划（拆模块 + 写任务卡 + Zone Plan + 用户确认）
> 2. 每个模块召唤《绘制子 Agent》按任务卡绘制
> 3. 每个模块召唤《检查子 Agent》独立验收
> 4. 不通过则整改闭环，直到 PASS
> 5. 用户人工确认后进入下一模块
>
> 详见 [orchestration/](orchestration/skill.md)。

### 1. 必须使用语义化 CLI 命令

> **⚠️ 三条禁令：**
> 1. **禁止使用 curl 调用 `/execute` 端点**——无论是绘制操作还是只读查询。所有操作（包括获取库 UUID、查询引脚、搜索器件）都必须通过 CLI 命令完成。如果 CLI 缺少所需命令，应先补充 CLI 命令而非退回 curl。
> 2. **禁止使用 `exec` 命令**——本 CLI 已移除 `exec`，强制使用语义化命令。
> 3. **禁止为单次操作编写脚本**（如 `*.mjs` / `*.py` / `*.js`）——所有操作直接用 CLI 命令完成。
>
> 所有放置、连线、验证操作必须通过：
> ```bash
> ./scripts/draw_cli.py <command> [options]
> # 或
> python3 scripts/draw_cli.py <command> [options]
> ```
> 输出格式：默认完整数据单行 JSON；`-v` 缩进 (调试)；`-q` 只输出 id (管道)。

### 2. 逐模块绘制，不能一次性摆完全部

> **禁止「先摆完全部器件，再统一连线」。**
>
> 正确方式：**模块A: 摆放→验证→连线→验证→人工确认 → 模块B**

### 3. 每个模块完成后必须人工确认

> 停下来等用户说"继续"才能进入下一个模块。

### 4. 批量操作粒度由 AI 自主判断

> 单次操作的器件数量不设硬性上限，由 AI 根据模块复杂度和置信度自主决策。
> 简单重复操作（如放 6 个相同开关 + 6 个二极管）可一次完成；复杂或高风险操作应分批并逐步确认。

### 5. 模块间禁止连接导线（MUST FOLLOW）

> **⚠️ 模块到模块也使用网络标签，禁止连接导线。**
>
> | 场景 | 连接方式 | 示例 |
> |------|----------|------|
> | **模块内部** | 导线直连 | 按键 SW1 → 二极管 D1 → 行线 |
> | **模块到模块** | 网络标签，禁止导线 | 矩阵模块 ROW0 ← NetPort → 主控模块 U1.D7 |
> | **模块到主控** | 网络标签，禁止导线 | U1.D7 → NetPort(ROW0) |
> | **电源/地** | NetFlag | VCC / GND |
>
> **判定标准**：如果导线的两端分别在两个不同功能模块的器件上，就是跨模块连接导线，必须改用网络标签。
>
> **为什么**：跨模块导线会导致原理图交叉凌乱、难以审阅、难以维护。网络标签让模块边界清晰，每个模块自包含。

---

## 脚本工具

本 skill 提供两个脚本文件，位于 `scripts/` 目录：

### 1. CLI 工具 (`scripts/draw_cli.py`, Python)

命令行直接执行常用操作，无需编写代码。已移除 `exec` 命令，强制使用语义化命令。

```bash
# 检查 bridge + EDA 连通性 (第一步必做)
./scripts/draw_cli.py health

# 查看项目信息
./scripts/draw_cli.py project-info

# 符号创建
./scripts/draw_cli.py create-symbol "nice!nano v2"          # 创建空符号到个人库
./scripts/draw_cli.py open-symbol <symbolUuid> <libUuid>    # 在编辑器中打开符号
./scripts/draw_cli.py create-device <name> <symUuid> <libUuid>  # 从符号创建器件
./scripts/draw_cli.py build-symbol <name> <pinCount>        # 一键构建符号骨架（创建+添加矩形+引脚）

# 符号编辑（只能在符号编辑器标签页中使用）
./scripts/draw_cli.py list-primitives       # 列出当前文档所有图元（符号/原理图通用）

# 搜索器件（默认系统库，--personal/--project 自动获取库 UUID）
./scripts/draw_cli.py search "nice!nano v2" 5
./scripts/draw_cli.py search "nice!nano v2" 5 --personal
./scripts/draw_cli.py search "nice!nano v2" 5 --project
./scripts/draw_cli.py search "nice!nano v2" 5 --lib <libraryUuid>

# 获取库 UUID（无需 curl）
./scripts/draw_cli.py lib-personal          # 个人库 UUID
./scripts/draw_cli.py lib-project           # 工程库 UUID
./scripts/draw_cli.py lib-list              # 列出所有库

# 清理页面（保留指定器件）
./scripts/draw_cli.py clear --keep id1,id2

# 放置器件 (推荐加 --name 标识型号/规格)
./scripts/draw_cli.py place <libUuid> <uuid> <x> <y> [designator] [rotation] [mirror]
./scripts/draw_cli.py place <libUuid> <uuid> <x> <y> R1 --name "10kΩ"
./scripts/draw_cli.py place <libUuid> <uuid> <x> <y> C1 --name "100nF"
./scripts/draw_cli.py place <libUuid> <uuid> <x> <y> U1 --name "nice!nano v2"

# 查询器件完整信息 (含 name/spec)
./scripts/draw_cli.py get-component <primitiveId>
./scripts/draw_cli.py get-component --designator U1

# 修改器件规格 (对已放置的器件补设 name)
./scripts/draw_cli.py modify-component <id> --name "10kΩ"
./scripts/draw_cli.py modify-component <id> --name "nRF52840"

# 添加文本
./scripts/draw_cli.py text <x> <y> "文本内容" [fontSize]

# 添加矩形
./scripts/draw_cli.py rect <x> <y> <w> <h> [color]

# 添加网络标签
./scripts/draw_cli.py netflag Power VCC 100 100

# 添加网络标��（延伸导线）
```bash
# ⚠️ NetPort 已不推荐。信号引脚仅用延伸导线（导线已含网络名）
# Power/Ground 仍用 NetFlag

# ✅ 信号引脚：仅延伸导线
./scripts/draw_cli.py wire "620,290,520,290" I2C_SDA

# ✅ 电源/地：导线 + NetFlag
./scripts/draw_cli.py wire "780,270,880,270" VCC && ./scripts/draw_cli.py netflag Power VCC 890 270

# ✅ 或一键替换已有 NetPort
./scripts/draw_cli.py replace-netports
```

# 绘制导线 (必须指定网络名, 防止跨网络合并)
./scripts/draw_cli.py wire "100,100,150,100" NET_NAME
./scripts/draw_cli.py wire "100,100,150,100" NET_NAME --safe    # 安全模式: 先查冲突再创建 (推荐)

# 批量创建导线 (逐条安全检查, 防止跨网络合并)
./scripts/draw_cli.py wire-batch --json '[{"points":[100,100,150,100],"net":"ROW0"},{"points":[200,100,250,100],"net":"ROW1"}]'

# 绘制导线（指定线型/颜色/线宽）
./scripts/draw_cli.py wire "100,100,200,100" NET --dashed
./scripts/draw_cli.py wire "100,100,200,100" NET --color "#ff0000" --width 5
# 线型选项: --dashed(短划线) --dotted(点线) --dot-dashed(点划线) 默认实线

# 查看导线详细信息
./scripts/draw_cli.py get-wire <primitiveId>

# 修改已有导线（网络名/坐标/线宽/线型）
./scripts/draw_cli.py modify-wire <id> --net "NEW_NET"
./scripts/draw_cli.py modify-wire <id> --line "100,200,150,200"

# 创建总线（多位信号线分组标识）
./scripts/draw_cli.py bus "DATA[7:0]" "100,200,300,200,300,500"
./scripts/draw_cli.py list-buses
./scripts/draw_cli.py delete-buses id1 id2

# 创建多边形/折线（装饰框，无电气属性）
./scripts/draw_cli.py polygon "100,100,300,100,300,300,100,300"
./scripts/draw_cli.py polygon "100,100,300,300" --dashed --fill "#f0f0f0"
./scripts/draw_cli.py list-polygons
./scripts/draw_cli.py delete-polygons id1 id2

# 搜索器件
./scripts/draw_cli.py search "电阻" 10

# 检查重叠
./scripts/draw_cli.py check-overlap

# 寻找空白区域放置器件（避免与已有器件重叠）
./scripts/draw_cli.py find-space 200 150 100 100 1200 900

# 获取器件引脚实际坐标（旋转/镜像后的真实位置）
./scripts/draw_cli.py get-pins <primitiveId>
./scripts/draw_cli.py get-pins --designator U1

# 删除器件（含周边导线/标签清理）
./scripts/draw_cli.py delete-component --designator J2
./scripts/draw_cli.py delete-component --designator J2 --margin 80 --clean-text

# 一站式放置器件 + 自动打标签（应用 U1 连线/标签心得）
# 引脚密集时，标签分散到不同方向（左/右/下交替），避免重叠
./scripts/draw_cli.py place-with-labels <libUuid> <uuid> <x> <y> <des> <rotation> \
  --pins "1:net=D1,type=port,side=right" "2:net=D0,type=port,side=bottom" "3:net=GND,type=ground,side=left" \
  --label "UART Debug"
# 可选 --find-space <w>,<h> 自动寻找空白区域

# 列出所有器件
./scripts/draw_cli.py list-components

# 收集网络标签快照 (按网络名分组, 含坐标)
./scripts/draw_cli.py collect-nets

# 生成网络摘要文本 (供下一个子 Agent 使用)
./scripts/draw_cli.py net-summary

# 缩放到所有图元
./scripts/draw_cli.py zoom-all

# 恢复标题块
./scripts/draw_cli.py restore-titleblock

# === 绘制中验证（每完成一个模块执行）===
./scripts/draw_cli.py verify-placement        # 验证摆放质量（重叠/间距/旋转）
./scripts/draw_cli.py verify-wiring            # 验证连线质量（网络名/标签重复/孤立标签）

# === 绘制后检查（每次提交前必须执行）===
./scripts/draw_cli.py drc                    # DRC 检查（聚合统计）
./scripts/draw_cli.py drc --show-ui           # 打开 DRC 面板查看逐项详细错误
./scripts/draw_cli.py check-duplicates       # 检测重复标签
./scripts/draw_cli.py check-duplicates --clean  # 检测并自动清理
./scripts/draw_cli.py check-wire-nets        # 检测无网络名的导线
./scripts/draw_cli.py check-wire-conflicts   # 检测导线网络冲突 (触碰不同网络的导线)
./scripts/draw_cli.py check-wire-through     # 检测导线穿过元器件本体
./scripts/draw_cli.py check-unconnected      # 检测未连接引脚 (4PIN开关感知+NC标记感知)
./scripts/draw_cli.py check-shorts           # 检测短路 (不同网络名导线连接在一起)
./scripts/draw_cli.py check-redundant-wires  # 检测冗余导线 (同两点间多条导线)
./scripts/draw_cli.py check-all              # 一键全量检查（7步，推荐）
```

> **⚠️ 已移除 `exec` 命令**——禁止执行任意 JS 代码字符串。所有操作必须使用上面的语义化命令。

### 2. 底层 API 参考（Bridge Server `/execute` 直调）

> **⚠️ JS 脚本已全部删除**：`draw-utils.js` 和 `draw-cli.mjs` 已于 Python 迁移完成后清理。以下代码示例仅作底层 EDA API 参考，展示每个语义命令对应的 JS 实现。**新操作必须使用 `draw_cli.py` 的语义化命令**，禁止再编写 JS 脚本。
> 如需查询某 API 的具体实现，请阅读 `scripts/draw_cli.py` 中对应函数的 `code` 字符串（即发送到 bridge server `/execute` 的 JS 代码）。

以下示例展示底层 API 调用方式（仅供理解，不要复制使用）：

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

// 清理页面
await utils.clearPage(['keepId1', 'keepId2']);

// 放置器件
const id = await utils.placeComponent({
  libraryUuid: '0819f05c4eef4c71ace90d822a990e87',
  uuid: '839559ae8f084f08bc76562033e67b08',
  x: 100, y: 150,
  designator: 'SW1',
});

// 批量放置
await utils.placeComponents([
  { libraryUuid, uuid, x: 100, y: 150, designator: 'SW1' },
  { libraryUuid, uuid, x: 250, y: 150, designator: 'SW2' },
]);

// 添加文本
await utils.addText({ x: 100, y: 120, text: 'Key Matrix', fontSize: 8 });

// 添加网络标签
await utils.addNetFlag({ type: 'Power', net: 'VCC', x: 100, y: 100 });

// 绘制导线（指定线型/颜色/线宽）
await utils.addWire([100, 100, 200, 100], 'NET_NAME', { color: '#ff0000', lineWidth: 3 });
await utils.addWire([100, 100, 200, 100], null, { lineType: 1 }); // 1=短划线

// 查看导线详细信息
const info = await utils.getWireInfo(wirePrimitiveId);
// { id, net, line, lineWidth, lineType, color }

// 修改已有导线
await utils.modifyWire(wireId, { net: 'NEW_NET', lineType: 0 }); // 0=实线

// 创建总线
await utils.addBus('DATA[7:0]', [100, 200, 300, 200, 300, 500]);

// 查询/删除总线
const buses = await utils.getAllBuses();
await utils.deleteBuses(busId);

// 创建多边形（装饰框）
await utils.addPolygon([100, 100, 300, 100, 300, 300, 100, 300], { fillColor: 'none' });
await utils.addPolygon([100, 100, 300, 100, 300, 300, 100, 300], { lineType: 1, fillColor: '#f0f0f0' });

// 查询/删除多边形
const polys = await utils.getAllPolygons();
await utils.deletePolygons(polyId);

// 检查重叠
const { overlaps } = await utils.checkOverlaps();

// 寻找空白区域（避免与已有器件重叠）
const pos = await utils.findFreeSpace({ width: 200, height: 150 });
if (pos) { /* pos.x, pos.y */ }

// 获取器件引脚实际坐标（旋转后的真实位置）
const pins = await utils.getComponentPins(primitiveId);

// 按位号查找器件 ID
const id = await utils.findComponentByDesignator('U1');

// 删除器件（含周边导线/标签清理，替代手写 cleanup 脚本）
await utils.deleteComponent({ designator: 'J2', margin: 80, cleanText: true });

// 一站式放置 + 自动打标��（应用 U1 连线/标签心得）
// 引脚密集时标签分散到不同方向，避免重叠
const result = await utils.placeComponentWithLabels({
  libraryUuid, uuid,
  x: 750, y: 150,
  designator: 'J2', rotation: 90,
  pinMap: {
    '1': { net: 'D1',  type: 'port',   side: 'right'  },
    '2': { net: 'D0',  type: 'port',   side: 'bottom' },
    '3': { net: 'GND', type: 'ground', side: 'left'   },
  },
  moduleLabel: 'UART Debug',
});

// === 绘制中验证函数 ===
// 验证器件摆放质量
const placement = await utils.verifyPlacement();

// 验证连线质量
const wiring = await utils.verifyWiring();

// === 绘制后检查函数 ===
// 运行 DRC
const drc = await utils.runDrc();
// 打开 DRC 面板查看逐项详细（showUi: true）
const drcDetail = await utils.runDrc({ showUi: true });

// 检测重复标签
const dup = await utils.detectDuplicateLabels();
// 自动清理重复标签
const cleaned = await utils.cleanupDuplicateLabels();

// 检查导线网络名
const wireNets = await utils.checkWireNets();

// 检测未连接引脚
const unconnected = await utils.detectUnconnectedPins();

// 检测导线穿过器件本体
const wireThrough = await utils.checkWireThroughComponent();

// 一键全量检查
const fullReport = await utils.runFullCheck();

// 创建自定义符号
const { symbolUuid, libraryUuid } = await utils.createSymbol("nice!nano v2");

// 在编辑器中打开符号（手动添加引脚和形状）
await utils.openSymbolInEditor(symbolUuid, libraryUuid);

// 从符号创建器件
const { deviceUuid } = await utils.createDeviceFromSymbol("nice!nano v2", symbolUuid, libraryUuid);
```

### 环境变量

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `EDA_BRIDGE_PORT` | 自动发现 | Bridge server 端口。未设置时 CLI 自动扫描 49620-49629 找到运行中的 bridge |

---

## 快速入口

```javascript
// 1. 确认当前文档为原理图
const doc = await eda.dmt_SelectControl.getCurrentDocumentInfo();
if (doc?.documentType !== 1) {
  return "Error: Not a schematic page";
}

// 2. 放置器件（rotation: 0/90/180/270，传 90 时内部存储为 270 但视觉正确）
const result = await eda.sch_PrimitiveComponent.create(
  { libraryUuid: "库UUID", uuid: "器件UUID" },
  x, y, "", 0, false, true, true
);

// 3. 设置位号
await eda.sch_PrimitiveComponent.modify(result?.primitiveId, {
  designator: "SW1"
});

// 4. 添加网络标签
await eda.sch_PrimitiveComponent.createNetFlag("Power", "VCC", x, y, 0, false);
```

---

## 符号编辑器注意事项

> **⚠️ 重要：引脚位置与矩形对齐**
>
> `SCH_PrimitivePin.create(x, y, ...)` 的 `(x, y)` 是**引脚连接点（圆点）**的位置，不是引脚端点。
> 引脚从连接点沿 `rotation` 方向延伸 `pinLength` 单位。
>
> **⚠️ 重要：rotation 参数会被 API 反转存储**
> 传 0 → 存储 180 → 引脚向左延伸（适合左侧引脚）
> 传 180 → 存储 0 → 引脚向右延伸（适合右侧引脚）
> 传 90 → 存储 270 → 引脚向下延伸（适合底部引脚）
> 传 270 → 存储 90 → 引脚向上延伸（适合顶部引脚）
>
> **正确做法：连接点放在矩形边缘**
> - 左侧引脚: `x = 矩形左边缘, rotation=0` → 传0→存180→向左延伸 ✅
> - 右侧引脚: `x = 矩形右边缘, rotation=180` → 传180→存0→向右延伸 ✅
>
> **错误示例：** `x=-100, 矩形左边缘=-80, rotation=0` → 连接点与矩形间有 20 单位间隙 ❌

> **⚠️ 重要：符号编辑器 Y 轴方向**
>
> 符号编辑器 Y 轴向上增长（数学坐标系），`sch_PrimitiveRectangle.create(topLeftX, topLeftY, ...)` 的 `topLeftY` 是矩形最高点（Y 值最大），矩形从此点向下延伸 `height`。
> 例如：要让矩形覆盖 y=-130 到 y=130，应传 `topLeftY=130, height=260`。

> **⚠️ 重要：符号编辑器与原理图编辑器 API 差异**
>
> 在符号编辑器（documentType=2）中，部分 `sch_Primitive*` 的批量查询方法不可用：
> - `sch_PrimitiveRectangle.getAll()` → ❌ 返回空数组
> - `sch_PrimitiveRectangle.getAllPrimitiveId()` → ❌ 返回空数组
> - `sch_PrimitiveCircle.getAll()` → ❌ 返回空数组
>
> 符号编辑器中查询图元的方法：
> - **查询所有引脚**: `sch_PrimitivePin.getAllPrimitiveId()` → 返回引脚 ID 数组
> - **查询所有选中图元（包括矩形/圆）**: 先选中图元，再调用 `sch_SelectControl.getAllSelectedPrimitives()`
> - CLI 命令: `selected-primitives` 列出当前选中的全部图元（符号编辑器专用）
>
> 正确做法：
> 1. 使用 `sch_PrimitivePin.getAllPrimitiveId()` 获取所有引脚 ID
> 2. 用 `sch_Primitive.getPrimitiveByPrimitiveId(id)` 逐个查询图元属性
> 3. 用 `sch_Primitive.getPrimitiveTypeByPrimitiveId(id)` 获取图元类型
> 4. 或使用 `sch_SelectControl.getAllSelectedPrimitives()` 获取已选中图元
>
> 详见 [symbol/](symbol/skill.md) 子 skill。

## 坐标系统

| 域 | 单位 | 换算 |
|---|---|---|
| 原理图 (SCH) | 0.01 inch = 10 mil = 0.254 mm | 100 单位 = 1 inch |
| PCB | 1 mil = 0.001 inch = 0.0254 mm | 1000 单位 = 1 inch |

**器件放置坐标 (x, y) 是符号原点/锚点，不是器件中心。**

---

## UUID 说明

**UUID 是 EasyEDA 系统库中每个器件的唯一标识符**，由 `libraryUuid`（库 UUID）和 `uuid`（器件 UUID）两部分组成。

### 为什么不能硬编码

- 不同用户、不同时间安装的库版本不同，同一器件的 UUID 可能不同
- 工程库和个人库中的器件 UUID 完全由用户自己定义
- 即使系统库，不同地区的镜像也可能存在差异

### 如何动态获取 UUID

**方法一：搜索（推荐）**

```javascript
// 在 EasyEDA 中搜索器件，获取 UUID
const results = await eda.lib_Device.search("SWITCH-轻触SMD_4PINS", "0819f05c4eef4c71ace90d822a990e87", undefined, undefined, 5, 1);
// results[0].uuid    → 器件 UUID
// results[0].libraryUuid → 库 UUID
```

**方法二：查看当前页面已有器件**

```javascript
// 获取页面上所有器件的 UUID 信息
const compIds = await eda.sch_PrimitiveComponent.getAllPrimitiveId();
for (const id of compIds) {
  const comps = await eda.sch_PrimitiveComponent.get([id]);
  const c = comps[0];
  console.log({
    designator: c.designator,
    name: c.name,
    libraryUuid: c.libraryUuid,
    uuid: c.uuid,
  });
}
```

**方法三：在 EasyEDA 界面中查找**

1. 在 EasyEDA 左侧「库」面板中搜索器件
2. 右键点击器件 → 属性 → 查看 `libraryUuid` 和 `uuid`
3. 或在「放置」→「器件」对话框中搜索，选中后查看底部信息

### 常用库 UUID（相对稳定，可缓存）

| 库 | UUID | 说明 |
|---|------|------|
| 系统库 | `0819f05c4eef4c71ace90d822a990e87` | EasyEDA 官方系统库，相对稳定 |
| 个人库 | 通过 `eda.lib_LibrariesList.getPersonalLibraryUuid()` 获取 | 每位用户不同 |
| 工程库 | 通过 `eda.lib_LibrariesList.getProjectLibraryUuid()` 获取 | 每个工程不同 |

---

## 自定义符号创建

当系统库中找不到所需器件的符号时（如开发板模块、定制 IC），需要创建自定义符号。

### 什么时候需要自定义符号

- 系统库搜索不到（如 `nice!nano v2`、`Pro Micro` 等开发板模块）
- 有现成符号但引脚功能与设计不匹配
- 需要自定义引脚排列以优化原理图可读性

### 创建流程

```
创建空符号 → 打开符号编辑器 → 添加引脚/形状/文本 → 创建器件绑定 → 在原理图中使用
```

### 操作步骤

**Step 1: 创建空符号**

```javascript
// 1. 获取个人库 UUID
const personalLib = await eda.lib_LibrariesList.getPersonalLibraryUuid();
if (!personalLib) return "Error: No personal library found";

// 2. 创建空符号
const symbolUuid = await eda.lib_Symbol.create(personalLib, "nice!nano v2");
if (!symbolUuid) return "Error: Failed to create symbol";
```

**Step 2: 在符号编辑器中编辑**

创建后通过 `openInEditor` 打开符号编辑器，在 EasyEDA 界面中手动添加引脚和形状：

```javascript
// 在编辑器中打开符号
await eda.lib_Symbol.openInEditor(symbolUuid, personalLib);
```

在 EasyEDA 符号编辑器中，使用工具栏添加：
- **矩形**: 绘制器件主体轮廓
- **引脚**: 放置引脚并设置名称/编号/电气属性
- **文本/图形**: 添加辅助说明

**Step 3: 创建器件（绑定符号和封装）**

```javascript
// 创建器件，关联符号（可选关联封装）
const deviceUuid = await eda.lib_Device.create(personalLib, "nice!nano v2", undefined, {
  symbolType: "sch",
  symbol: { uuid: symbolUuid, libraryUuid: personalLib },
  // footprint: { uuid: footprintUuid, libraryUuid: personalLib }, // 可选
});
```

**Step 4: 在原理图中使用**

```javascript
// 搜索自定义器件
const results = await eda.lib_Device.search("nice!nano v2", personalLib);
if (results.length === 0) return "Error: Device not found";

// 放置器件
const comp = await eda.sch_PrimitiveComponent.create(
  { libraryUuid: personalLib, uuid: results[0].uuid },
  x, y, "", 0, false, true, true
);
await eda.sch_PrimitiveComponent.modify(comp?.primitiveId, { designator: "U1" });
```

### 符号文档源码格式（进阶）

如果不想手动编辑，也可以通过 `LIB_Symbol.updateDocumentSource()` 直接设置符号的文档源码。格式如下：

```javascript
// 构建符号文档源码
const source = [
  `{ "type": "DOCHEAD" }||{ "docType": "SYMBOL", "uuid": "${symbolUuid}", "client": "clientID" }|`,
  `{ "type": "CANVAS", "ticket": 1 }||{ "originX":0, "originY":0 }|`,
  `{ "type": "PART","id":"", "ticket": 1 }||{"BBOX": [-100, -100, 100, 100]}|`,
  // 矩形主体
  `{ "type": "RECT", "id": "r1", "ticket": 1 }||{ "partId": "", "groupId": 0, "locked": false, "zIndex": 7.35, "dotX1": -80, "dotY1": -80, "dotX2": 80, "dotY2": 80, "radiusX": 0, "radiusY": 0, "rotation": 0, "strokeColor": null, "strokeStyle": 0, "fillColor": "", "strokeWidth": null, "fillStyle": 1 }|`,
  // 引脚（需为每个引脚定义 PIN + ATTR）
  `{ "type": "PIN", "id": "p1", "ticket": 1 }||{ "partId": "", "groupId": 0, "locked": false, "zIndex": 2.8772, "display": true, "electric": 2, "positionX": -100, "positionY": -60, "length": 20, "rotation": 0, "color": "#880000", "pinShape": 0 }|`,
  `{ "type": "ATTR", "id": "a1", "ticket": 1 }||{ "partId": "", "groupId": 0, "locked": true, "zIndex": 0.1, "parentId": "p1", "key": "NAME", "value": "D0/TX", "keyVisible": true, "valueVisible": true, "positionX": -120, "positionY": -60, "rotation": 0, "color": null, "fillColor": null, "fontFamily": null, "fontSize": null, "strikeout": null, "underline": null, "italic": null, "fontWeight": null, "vAlign": 0, "hAlign": 2 }|`,
  `{ "type": "ATTR", "id": "a2", "ticket": 1 }||{ "partId": "", "groupId": 0, "locked": true, "zIndex": 0.1, "parentId": "p1", "key": "NUMBER", "value": "1", "keyVisible": true, "valueVisible": true, "positionX": -120, "positionY": -60, "rotation": 0, "color": null, "fillColor": null, "fontFamily": null, "fontSize": null, "strikeout": null, "underline": null, "italic": null, "fontWeight": null, "vAlign": 0, "hAlign": 2 }|`,
].join("\n");

// 应用文档源码
await eda.lib_Symbol.updateDocumentSource(symbolUuid, personalLib, source);
```

注意：引脚坐标中，`positionX/Y` 是引脚最外端的坐标（远离符号主体的那一端），引脚长度由 `length` 控制。`rotation` 决定了引脚方向（0=左, 90=上, 180=右, 270=下）。

### 引脚属性说明

| PIN 属性 | 说明 | 值 |
|---------|------|-----|
| `electric` | 电气特性 | 0=UNKNOWN 1=INPUT 2=OUTPUT 3=BI |
| `rotation` | 引脚方向 | 0=向左 90=向上 180=向右 270=向下 |
| `length` | 引脚长度 | 单位与原理图坐标一致 |
| `pinShape` | 引脚样式 | 0=无 1=Clock 2=DOT（可组合 3=Clock\|DOT） |

### ATTR 属性说明

每个 PIN 必须有两个 ATTR：
- `key="NAME"`, `value="引脚名"` — 引脚名称，显示在图纸上
- `key="NUMBER"`, `value="引脚编号"` — 引脚编号（序号）

`parentId` 必须指向对应的 PIN 的 `id`。

---

## 常用器件搜索关键词

> **⚠️ 以下 UUID 仅作示例参考，实际使用时必须通过搜索动态获取。**

| 器件 | 搜索关键词 | 说明 |
|------|-----------|------|
| 轻触开关 | `SWITCH-轻触SMD_4PINS` | 4 脚 SMD 轻触开关 |
| 二极管 B5819WS | `B5819WS` | 肖特基二极管 |
| 旋转编码器 | `EC11B152442D-STEC11B03` | EC11 旋转编码器 |
| 4P Header | `JP4` | 4 脚排针 |
| OLED 显示屏 | `SSD1306-330MT` | SSD1306 OLED |
| 电阻 0402 | `0402WGF` | 0402 封装贴片电阻 |
| 电容 0402 | `0402CG` | 0402 封装贴片电容 |
| nRF52840 模块 | `nRF52840` | 主控模块，搜索后选具体型号 |

