# 创建符号（本地 / 服务端）

> 示例：
> - `vjcad/example/insert/symbol/01createlocal.js` — IndexedDB 本地符号
> - `vjcad/example/insert/symbol/02createserver.js` — 服务端符号

vjcad 把"符号"理解为一组实体 + 基点 + 缩略图打包。可以保存到 **本地 IndexedDB** 或 **服务端**，两条路径对应两个 service。

## 1. 通用步骤

```mermaid
flowchart LR
    A[准备实体数组<br/>line/circle/...] --> B[buildSymbolVcadData<br/>(entities, false)]
    A --> C[generateThumbnailFromEntities<br/>(entities, 128, 128)]
    B --> D{保存目标}
    D -->|本地| E[localService.saveLocalSymbol]
    D -->|服务端| F[compressSymbolData → uint8ArrayToBase64<br/>→ service.createSymbol]
```

`vcadData` 是符号几何的序列化字符串；缩略图是完整的 Data URL（`data:image/png;base64,...`，128×128），可直接当 `<img src>`，**不要再拼 `data:` 前缀**。

## 2. 保存到本地（IndexedDB）

```js
const localService = getLocalStorageService();

// 2.1 找/建分类
const categories = await localService.getLocalSymbolCategories();
let cat = categories.find(c => c.name === "示例符号");
if (!cat) cat = await localService.createLocalSymbolCategory("示例符号");

// 2.2 准备实体（必须先 setDefaults）
const line1 = new LineEnt([-10, 0], [10, 0]);
const line2 = new LineEnt([0, -10], [0, 10]);
const circle = new CircleEnt([0, 0], 8);
[line1, line2, circle].forEach(e => e.setDefaults());
Engine.addEntities([line1, line2, circle]); // 可选：在画布预览

// 2.3 打包数据 + 生成缩略图
const entities = [line1, line2, circle];
const vcadData  = await buildSymbolVcadData(entities, /* clearSourceInfo */ false);
const thumbnail = await generateThumbnailFromEntities(entities, 128, 128);

// 2.4 保存
const symbol = await localService.saveLocalSymbol({
    categoryId: cat.id,
    name: "十字圆符号",
    basePoint: [0, 0],
    thumbnail,
    vcadData,
});
```

> 完整代码：`vjcad/example/insert/symbol/01createlocal.js`

## 3. 保存到服务端

服务端要求 vcadData 经过 **gzip 压缩 + base64 编码**：

```js
let service;
try {
    service = Engine.service;          // 未配置 serviceUrl 时 getter 直接抛错，不会返回空
} catch (e) {
    throw new Error("服务不可用：未配置 serviceUrl");
}

// 3.1 找/建分类
const categories = await service.getSymbolCategories();
let cat = categories.find(c => c.name === "用户自定义");
if (!cat) cat = await service.createSymbolCategory("用户自定义");

// 3.2 实体 → vcadData → 压缩 → base64
const vcadData       = await buildSymbolVcadData(entities, false);
const thumbnail      = await generateThumbnailFromEntities(entities, 128, 128);
const compressedData = await compressSymbolData(vcadData, /* level */ 6);
const base64Data     = uint8ArrayToBase64(compressedData);

// 3.3 上传
const sym = await service.createSymbol({
    categoryId: cat.id,
    name: "箭头符号_" + Date.now(),
    basePoint: [0, 0],
    thumbnail,
    data: base64Data,
});
```

> 完整代码：`vjcad/example/insert/symbol/02createserver.js`

## 4. API 速查

| API | 来源 | 作用 |
|---|---|---|
| `buildSymbolVcadData(entities, clearSourceInfo?)` | vjcad 顶层 | 实体 → vcad 字符串 |
| `generateThumbnailFromEntities(entities, w, h)` | vjcad 顶层 | 缩略图 PNG Data URL（`data:image/png;base64,...`） |
| `compressSymbolData(data, level?)` / `uint8ArrayToBase64(arr)` | vjcad 顶层 | 服务端上传前压缩 |
| `getLocalStorageService()` | vjcad 顶层 | IndexedDB 服务实例 |
| `localService.getLocalSymbolCategories() / createLocalSymbolCategory(name)` | LocalStorageService | 分类 |
| `localService.saveLocalSymbol({ categoryId, name, basePoint, thumbnail, vcadData })` | LocalStorageService | 保存 |
| `Engine.service.getSymbolCategories() / createSymbolCategory(name)` | service | 服务端分类 |
| `Engine.service.createSymbol({ categoryId, name, basePoint, thumbnail, data })` | service | 服务端创建符号 |

## 红旗

- **`buildSymbolVcadData(entities, clearSourceInfo)` 第二参数是是否清除来源信息（非压缩）；压缩是服务端路径单独用 `compressSymbolData(vcadData, level)` 完成，本地保存直接存 vcad 字符串**
- `generateThumbnailFromEntities` 是 async，必须 `await`
- 服务端创建分类需要后台权限，匿名 / 只读账号会失败，需要 try/catch
- 保存前的实体一定要 `setDefaults()`，否则色 / 图层信息丢失
- 重名符号会创建多份，建议加时间戳后缀避免冲突

## 参考（官方文档 MCP）

- 示例：
- `vjcad/example/insert/symbol/01createlocal.js` — IndexedDB 本地符号
- `vjcad/example/insert/symbol/02createserver.js` — 服务端符号
