> **本文回答**：`PreviewManager.setPreviewEntities / clear` —— 绘制命令里的橡皮筋实时预览。

## 一、是什么

用户在点第二个点之前，鼠标在动 → 实时显示一条"临时直线"，跟随鼠标 → 点击确认才落地成真实实体。

这条"临时直线"就是 **预览实体**，由 `PreviewManager` 管。

## 二、API

```ts
ctx.previewManager.setPreviewEntities(entities: EntityBase[]): void
ctx.previewManager.setPreviewFeatures(features: GeoJSON.Feature[]): void
ctx.previewManager.setPolylineVertices(points: Point2D[]): void  // 折线绘制中记录已确认顶点
ctx.previewManager.clearPolylineVertices(): void
ctx.previewManager.clear(): void        // 注意：方法名是 clear()，不是 clearPreview()
ctx.previewManager.getFeatures(): GeoJSON.Feature[]
ctx.previewManager.setPreviewColor(hex: string | null): void  // 覆盖预览色（默认跟 drawingDefaults.color）
```

## 三、和 `getPoint` 的 callback 搭配

`PointInputOptions.callback`（字段名是 `callback`，不是 `onMouseMove`）在光标移动时触发，参数是当前 CAD 坐标点：

```js
class PreviewLineCmd {
  async main(ctx) {   // 实例方法，不能写 static
    const { inputManager: im, previewManager: pm, store, drawingDefaults: defaults } = ctx;

    const p1 = await im.getPoint({ message: '指定起点' });
    if (p1.status !== 'ok') return;

    const p2 = await im.getPoint({
      message: '指定终点',
      useBasePoint: true,            // 橡皮筋开关是 useBasePoint，不是 rubberBand
      basePoint: p1.value,           // 点结果在 .value，不是 .point
      callback: cur => {
        const preview = new vjmapext.LineEnt(p1.value, cur);   // 预览色由 pm 统一覆盖，设 color 无效
        pm.setPreviewEntities([preview]);
      }
    });

    pm.clear();              // 无论成功取消都清
    if (p2.status !== 'ok') return;

    const line = new vjmapext.LineEnt(p1.value, p2.value);
    line.color = defaults.color;
    store.add(line);
  }
}
```

## 四、多预览实体

```js
callback: cur => {
  const line1 = new vjmapext.LineEnt(p1.value, cur);
  const line2 = new vjmapext.LineEnt(p2.value, cur);
  const marker = new vjmapext.CircleEnt(cur, 5);
  pm.setPreviewEntities([line1, line2, marker]);
}
```

内部会把整组合并一次绘制，不闪。

## 五、getFeatures（序列化用）

```js
const feats = pm.getFeatures();
// GeoJSON.Feature[]，可叠加到其他图层或调试打印
```

## 六、与内置橡皮筋的区别

| 对象 | 内置橡皮筋 | setPreviewEntities |
|---|---|---|
| 触发 | `getPoint({ useBasePoint: true, basePoint })` | 显式 set |
| 形状 | 只能一条直线 | 任意实体数组 |
| 样式 | 系统虚线 | **不继承**实体颜色/线宽：`color` 被预览色覆盖（默认 = `drawingDefaults.color`），线一律 1px 虚线、点 4px；只保留多边形的 `fillColor`/`fillOpacity` |
| 用途 | 标准距离 / 方向显示 | 复杂形状 / 多对象预览 |

一般：

- 画 LINE → 只用橡皮筋够了
- 画 RECTPOLY / CIRCLE / 带文字的标注 → 用 setPreviewEntities

## 七、性能

`setPreviewEntities` 每次调用**替换**上一次内容；内部做了重绘合并，不需要手动 RAF。

但 callback 每帧都 fire → 如果每帧生成 100+ 预览实体可能卡；建议复用同一个实体对象：

```js
const preview = new vjmapext.LineEnt(p1.value, p1.value);
callback: cur => {
  preview.endPoint = cur;
  pm.setPreviewEntities([preview]);
}
```

## 八、预览 Hatch 填充

```js
callback: cur => {
  const loop = [
    p1.value,
    [cur.x, p1.value.y],   // 环用 [x,y] 数组（Point2DLike）
    cur,
    [p1.value.x, cur.y],
    p1.value
  ];
  const hatch = new vjmapext.HatchEnt(loop);   // 构造收扁平点数组（>=3 点成 1 个 outer 环）
  hatch.fillColor = 0xff0000;
  hatch.fillOpacity = 0.3;
  pm.setPreviewEntities([hatch]);
}
```

画矩形填充时的预览。

## 九、命令取消时的清理

```js
try {
  const p = await im.getPoint({ message: '...', callback: cur => { ... } });
  ...
} finally {
  pm.clear();   // 无论 OK / Cancel 都清
}
```

或用 CommandContext 的生命周期：命令流程自然结束时 PreviewManager 会清掉，但**显式 clear 更保险**。

## 十、常见坑

1. **忘 `pm.clear()`**：预览实体残留画面上（注意不是 `clearPreview()`，那个方法不存在）。
2. **预览实体被加到 store**：预览**不是** store 里的；不要 `store.add(preview)`；只 `setPreviewEntities`。
3. **每帧 new 100 次 Entity**：GC 压力大；复用同一个实体对象。
4. **预览颜色 = drawingDefaults.color 看不清**：给预览实体设 `color` 没用（会被统一覆盖）；改用 `pm.setPreviewColor('#ff00ff')` 换预览色（注意 `setDrawingDefaults` 会把它同步回默认色）。

## 相关

- 点输入 → [input-manager](input-manager.md)
- 自定义命令 → [custom-command](custom-command.md)
- 实体样式 → [entity/style-props](../entity/style-props.md)
