# 官方插件指南

> 文档：`vjcad/doc/plugin/plugin-annotation.md`、`vjcad/doc/plugin/plugin-architecture.md`
> 示例：`vjcad/example/plugin/05architecture.js`

vjcad 自带几个官方插件，**理解它们就掌握了"标准插件长什么样"**。

| 插件 | 包名 | 功能 | 主要扩展点 |
|---|---|---|---|
| 批注 | `vcad-plugin-annotation` | 云线 / 箭头 / 矩形 / 文字 / 引线 / 自由线 / 图章 / 高亮 / 显隐 / 清除 / 列表 | Ribbon 「审阅」标签、命令 `ANNOTATION`、`ANNO_*` 系列 |
| 建筑 | `vcad-plugin-architecture` | 轴网 / 柱 / 墙 / 门窗 / 楼梯 / 阳台 / 房间 / 标高 / 剖切 / 指北针 | Ribbon 「建筑」标签、命令 `BIN*`、`window.vjcadArchEntities` 暴露实体类 |
| 三维视图 | `vcad-plugin-view3d` | 三维查看 | 命令 `VIEW3D`，标准 PluginManager 用法的最小骨架 |
| 插入 / 图片矢量化 | `vcad-plugin-insert` | 插入图片（内部 `VectroizeService` 矢量化）、插入/提取表格 | 插件自己注册命令 `INSERTIMAGE` / `INSERTTABLE` / `EXTRACTTABLE`（核心库无这些命令），挂到 Ribbon `insert` 标签 |
| AI 助手 | `vcad-plugin-aiassist` | AI 对话面板 | 命令 `AIASSISTANT2` |
| 网络图 | `vcad-plugin-network-graph` | 网络拓扑图 | 命令 `DRAWNETWORKGRAPH` / `GENERATENETWORK` / `MERGENODES` / `BREAKBRANCH` / `REVERSEBRANCH` |

## 1. 批注插件（annotation-plugin）

### 命令一览

| 工具 | 命令 |
|---|---|
| 主面板 | `ANNOTATION` |
| 云线 | `ANNO_CLOUD` |
| 箭头（单/双/折） | `ANNO_ARROW` |
| 矩形 / 椭圆 | `ANNO_RECT` / `ANNO_ELLIPSE` |
| 文字 / 引线 | `ANNO_TEXT` / `ANNO_LEADER` |
| 自由线 | `ANNO_FREEHAND` |
| 图章 | `ANNO_STAMP`（1=批准 2=驳回 3=审核 4=修改 5=作废 6=草稿） |
| 高亮 | `ANNO_HIGHLIGHT` |
| 显示 / 隐藏 / 清除 / 列表 | `ANNO_SHOW` / `ANNO_HIDE` / `ANNO_CLEAR` / `ANNO_LIST` |

### 设计要点

- **专属图层** `_ANNOTATION`：所有批注实体落入此图层，颜色 ACI 1（红）
- **自适应线宽**：批注用多段线 `globalWidth`（不是 `lineWeight`），按 WCS 对角线 `/500` 计算
- **自适应弧长**：云线弧长 = WCS 对角线 `/30`
- **命令委托**：`ANNO_TEXT` / `ANNO_LEADER` 通过 `setTimeout` 异步调度委托给 `MTEXT` / `MLEADER`，避免命令嵌套

### 加载

```js
const pm = PluginManager.getInstance();
await pm.loadFromUrl('./plugins/vcad-plugin-annotation.js');
// 或
await pm.loadByName('vcad-plugin-annotation');
```

激活后：

```js
await Engine.editor.executerWithOp('ANNOTATION');   // 打开浮动工具栏
```

## 2. 建筑插件（architecture-plugin）

### 命令分类

| 分类 | 命令 |
|---|---|
| 轴网 | `BINDRAWAXISGRID` / `BINLABELAXIS` |
| 柱子 | `BINDRAWCOLUMN` / `BINCOLUMNTOAXIS` |
| 墙体 | `BINDRAWWALL` / `BINWALLFROMAXIS` / `BINEDITWALL` |
| 门窗 | `BININSERTDOOR` / `BININSERTWINDOW` |
| 楼梯 / 阳台 | `BINDRAWSTAIR` / `BINDRAWBALCONY` |
| 房间 | `BINSEARCHROOM` / `BINLABELROOM` |
| 符号 | `BINELEVMARK` / `BINSECTIONMARK` / `BINNORTHARROW` / `BININDEXMARK` |
| 标注 | `BINARCHDIM` / `BINAXISDIM` |
| 工具 | `BINARCHTOOLBAR` / `BINDEMOFLOORPLAN` |

### 加载与 SDK API

```js
const pm = PluginManager.getInstance();
await pm.loadByName('vcad-plugin-architecture');

const {
    AxisGridEnt, ColumnEnt, WallEnt,
    DoorEnt, WindowEnt, StairEnt, BalconyEnt,
    ElevationMarkEnt, SectionMarkEnt
} = window.vjcadArchEntities;

// 例：建轴网
const grid = AxisGridEnt.create(
    new Point2D(0, 0),
    [3600, 3900, 5700],   // 横向间距
    [4200, 2100, 4200]    // 纵向间距
);
Engine.addEntities([grid]);

// 例：墙体（厚 240，中线对齐）
const wall = WallEnt.create(new Point2D(0, 0), new Point2D(13200, 0), 240);
Engine.addEntities([wall]);

// 例：门 + 关联墙
const door = DoorEnt.create(new Point2D(5000, 0), 'single', 900);
door.rotation = 0;
door.wallThickness = 240;

Engine.addEntities([door]);     // 必须先 add 才有 id
door.wallHandle = String(wall.id);
wall.doorWindowHandles = [String(door.id)];
wall.setModified();             // 触发墙体重绘 / 自动开洞
```

> 完整两室两厅户型例子：`vjcad/example/plugin/05architecture.js`

### 关联机制（核心）

- 墙体 `WallEnt.doorWindowHandles: string[]`、门窗 `wallHandle: string` 双向引用
- `WallEnt.buildNestEnts()` 在绘制时按 `doorWindowHandles` **自动开洞**
- 门窗夹点编辑时被 **约束在墙中线**
- 墙体移动时 `WallService` 监听 `EntityModified` 自动 **重投影门窗** 位置
- 门窗删除前 `WallService` 监听 `EntitiesErasing` 把对应 ID 从 `doorWindowHandles` 移除，墙体补洞

### 国标图层（自动创建）

| 图层 | 颜色 | 用途 |
|---|---|---|
| `A_AXIS` | 1 红 | 轴线 |
| `A_COLU` | 2 黄 | 柱子 |
| `A_WALL` | 7 白 | 墙体 |
| `A_DOOR` / `A_WIND` | 4 青 | 门 / 窗 |
| `A_STRS` / `A_BALC` | 3 绿 | 楼梯 / 阳台 |
| `A_ROOM` | 6 品红 | 房间 |
| `A_DIMS` | 7 白 | 标注 |
| `A_SYMB` | 1 红 | 符号 |

## 3. 三维视图插件（view3d-plugin，最小骨架）

官方插件里最精简的完整插件，源码结构就是上文"标准 Plugin 接口"的最小实现，可以拷贝它的 `index.ts` 当起点：

- `manifest` 必填字段示范（`id: 'view3d'`）
- `onActivate` 中 `registerIcon('VIEW3D')` → `registerCommand('VIEW3D', ..., View3dCommand)` → `addMenuItem('tool', …)` → `addRibbonGroup('plugins', …)` 的顺序
- `onDeactivate` 只需销毁插件自建的 UI（源码里仅 `destroyView3dPanel()`）；命令/菜单/Ribbon/`on` 事件等注册项由 PluginManager 调 `context._cleanup()` 自动注销，无需手写逆序注销（图标与 `once` 监听不在清理范围，侧栏面板仅 `unload` 时移除）

参考实现：`vjcad/example/plugin/04pluginmanager.js`（运行时构造 `pluginCode` 并 `loadFromContent` 加载，等价于真实文件）。

## 4. insert-plugin（图片矢量化）

`INSERTIMAGE` / `INSERTTABLE` / `EXTRACTTABLE` 三个命令都由 insert 插件（`manifest.id: 'insert'`）在 `onActivate` 里**自己注册**，核心库没有；插件未加载时执行这些命令无效果。图片矢量化用插件内部的 `VectroizeService`（WASM）。插件 dist 带 `vcad-plugin-insert.css`，而 `loadByName` 不加载 CSS，推荐：

```js
await pm.loadFromUrl('./plugins/vcad-plugin-insert.js', './plugins/vcad-plugin-insert.css');
// 或（只有 JS，样式缺失）
await pm.loadByName('vcad-plugin-insert');          // 包名是 vcad-plugin-insert，构建产物 vcad-plugin-insert.js
```

API 用法见 `insert/vectorize.md`。

## 红旗

- **加载顺序**：建筑插件依赖批注？依赖 dim？目前各官方插件相互独立，但自定义插件如果有 `dependencies`，先 activate 依赖，再 activate 自身
- 建筑插件实体使用 `*.create()` 工厂方法，**不要** `new WallEnt(...)`：构造函数无参，传进去的起终点/厚度会被忽略，得到默认墙（0,0→1000,0）
- 关联门窗时 `Engine.addEntities` 之后才有 `entity.id`，需要在 add 后再设置 `wallHandle / doorWindowHandles`
- `wall.setModified()` 是触发开洞重绘的开关，新增/解除关联后必须调用
- 批注插件的 `_ANNOTATION` 图层由 `ensureLayer()` 按需创建（默认可见，颜色 ACI 1）；`ANNO_HIDE` 只是把它 `layerOn=false`（若它是当前图层会先切回 `0`）。保存图纸时图层会随文档带过去
- `vjcad/example/plugin/05architecture.js` 需要 **先构建并复制建筑插件 dist 文件** 到 playground 才能跑

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/plugin/plugin-annotation.md`、`vjcad/doc/plugin/plugin-architecture.md`
- 示例：`vjcad/example/plugin/05architecture.js`
