> **本文回答**：拖动 / 取点过程中的"实时预览图"用 `Engine.drawPreviewEntity(ent)` / `Engine.drawPreviewEntities([e1, e2])` + `Engine.clearPreview()` 控制；通常配合 `PointInputOptions.callback`（鼠标移动时被调用，参数是画布坐标 → `Engine.canvasToWcs(canvasPt)` 转世界坐标）。命令结束 / 异常时一定要 `Engine.clearPreview()`。

## 一、最小预览（圆心 + 拖半径）

```js
class CirclePreviewCommand {
  constructor() { this.center = null; }

  async main() {
    Engine.undoManager.start_undoMark('绘制圆');
    try {
      if (!await this.getCenter()) return;
      const r = await this.getRadius();
      if (r == null) return;
      const c = new CircleEnt([this.center.x, this.center.y], r);
      c.setDefaults();
      Engine.addEntities(c);
    } finally {
      Engine.undoManager.end_undoMark();
      Engine.clearPreview();
    }
  }

  async getCenter() {
    const opt = new PointInputOptions('指定圆心:');
    const r = await Engine.getPoint(opt);
    if (r.status !== InputStatusEnum.OK) return false;
    this.center = r.value;
    return true;
  }

  async getRadius() {
    const opt = new PointInputOptions('指定半径:');
    opt.useBasePoint = true;
    opt.basePoint = this.center;

    opt.callback = (canvasPt) => {
      const wp = Engine.canvasToWcs(canvasPt);
      const r = Math.hypot(wp.x - this.center.x, wp.y - this.center.y);
      if (r > 0) {
        const preview = new CircleEnt([this.center.x, this.center.y], r);
        preview.setDefaults();
        Engine.clearPreview();
        Engine.drawPreviewEntity(preview);
      }
    };

    const r = await Engine.getPoint(opt);
    if (r.status !== InputStatusEnum.OK) return null;
    return Math.hypot(r.value.x - this.center.x, r.value.y - this.center.y);
  }
}
```

MCP 参考：`vjcad/example/command/03withpreview.js`。

## 二、隐藏默认橡皮筋线 + 自定义预览

```js
const opt = new PointInputOptions('指定终点:');
opt.useBasePoint   = true;
opt.basePoint      = startPoint;
opt.hideRubberBand = true;   // 关掉默认橡皮筋

opt.callback = (canvasPt) => {
  const wp = Engine.canvasToWcs(canvasPt);
  const previewLine   = new LineEnt([startPoint.x, startPoint.y], [wp.x, wp.y]);
  const endpointMark  = new CircleEnt([wp.x, wp.y], 3);
  previewLine.setDefaults();  endpointMark.setDefaults();
  Engine.clearPreview();
  Engine.drawPreviewEntities([previewLine, endpointMark]);
};
```

MCP 参考：`vjcad/example/input/02getpointwithbase.js`。

## 三、底层等价 API（`pcanvas.drawControl`）

```js
const dc = Engine.pcanvas.drawControl;
dc.drawPreviewEntity(ent);
dc.drawPreviewEntities([e1, e2]);
dc.drawHighLightEntity(ent);
dc.previewGraphics.clear();

dc.setPreviewPosition(point);
dc.setPreviewRotation(angle);
dc.setPreviewScale(sx, sy);
dc.resetPreview();
```

MCP 参考：`vjcad/doc/command/api-reference.md`。

便捷封装（推荐）：

| 便捷 API | 等价 |
|---|---|
| `Engine.drawPreviewEntity(ent)` | `Engine.pcanvas.drawControl.drawPreviewEntity(ent)` |
| `Engine.drawPreviewEntities([e1, e2])` | `Engine.pcanvas.drawControl.drawPreviewEntities([...])` |
| `Engine.clearPreview()` | `Engine.pcanvas.drawControl.clearPreview()`（先移除 MText 等子容器再 `previewGraphics.clear()`；只调 `previewGraphics.clear()` 清不掉 MText 预览） |

来源：示例 `vjcad/example/command/03withpreview.js`、`vjcad/example/input/02getpointwithbase.js` 中实际用 `Engine.drawPreviewEntity / Engine.clearPreview`。

## 四、注意

1. `callback` 拿到的是**画布坐标**，必须 `Engine.canvasToWcs(canvasPt)` 转世界坐标后再算长度 / 角度。
2. 每次 `callback` 触发都要先 `Engine.clearPreview()`，否则预览图会重叠。
3. 注册命令的 `main()` 返回（或抛异常）后，框架会自动 `drawControl.resetPreview()`，`finally` 里再 `Engine.clearPreview()` 属防御写法；**脚本里直接调 `getPoint` 画预览（不经命令框架）时才必须自己清**。Esc 让输入返回 `status === InputStatusEnum.Cancel`，不会抛异常——`finally` 主要用于保证 `end_undoMark()` 执行。

## 反例

- 直接把 `canvasPt.x / canvasPt.y` 当作图纸坐标：会跟随屏幕不跟随图纸缩放。
- `callback` 里不 clear 直接 `drawPreviewEntity`：预览图随鼠标增长而堆叠。

## 参考（官方文档 MCP）

- 文档：`vjcad/doc/command/index.md`、`vjcad/doc/command/api-reference.md`
- 示例：`vjcad/example/command/03withpreview.js`、`vjcad/example/input/02getpointwithbase.js`、`vjcad/example/input/06getkeyword.js`
