唯杰地图 logo唯杰地图
VJMAP
EN

custom-command

vjmapext/references/drawing/custom-command.md 7.1 KB202 行
原文

本文回答:CommandRegistry.register —— 写一条完整自定义命令:输入 / 预览 / 事务 / 选项。

一、命令类最小骨架

class MyCmd {
  // ⚠️ 必须是实例方法:registry 内部 `new cmdClass()` 后调 `cmd.main(ctx, opts)`,
  // 写成 static 会得到 "cmd.main is not a function"(被 execute 的 try/catch 吞掉)
  async main(ctx, opts) {
    // ctx: CommandContext(含 store / inputManager / previewManager / ...)
    // opts: executeCommand(name, opts) 的第二个参数
  }
}

二、注册

mapcadLayer.commandRegistry.register(
  'MY_CMD',         // 建议大写
  MyCmd,
  '我的命令'        // 可选描述
);

mapcadLayer.executeCommand('MY_CMD');
mapcadLayer.executeCommand('MY_CMD', { color: 0xff0000 });

三、CommandContext 速查

字段 用途
store add(ent) / delete(id) / get(id) / getAll()
inputManager getPoint / getEntity / getSelections / getReal / ...
previewManager setPreviewEntities / clear
undoManager beginTransaction / endTransaction
gripManager 编辑夹点
drawingDefaults 当前默认颜色 / 填充色 / 图层(无文字高字段,见 serialize.md)
blocks 块定义集合
coordMapper 坐标转换(像素 / CAD)
eventBus fire 业务事件
map vjmap.Map 实例

四、完整示例:画星形

class DrawStarCmd {
  async main(ctx, opts = {}) {
    const { inputManager: im, previewManager: pm, store, undoManager: um, drawingDefaults: defaults } = ctx;

    const ptsCount = opts.points ?? 5;

    const cr = await im.getPoint({ message: '指定星形中心' });
    if (cr.status !== 'ok') return;
    const center = cr.value;   // 点结果在 .value,不是 .point

    const rr = await im.getPoint({
      message: '指定外径点',
      useBasePoint: true,      // 橡皮筋开关是 useBasePoint,不是 rubberBand
      basePoint: center,
      callback: p => {          // 光标移动回调字段名是 callback,不是 onMouseMove
        const ent = buildStar(center, center.distanceTo(p), ptsCount, defaults);
        pm.setPreviewEntities([ent]);
      }
    });
    pm.clear();                // PreviewManager 上是 clear(),不是 clearPreview()
    if (rr.status !== 'ok') return;

    um.beginTransaction();  // 画星形
    const star = buildStar(center, center.distanceTo(rr.value), ptsCount, defaults);
    store.add(star);
    um.endTransaction();
  }
}

function buildStar(center, r, n, defaults) {
  const pts = [];
  for (let i = 0; i < n * 2; i++) {
    const a = (Math.PI * i) / n - Math.PI / 2;
    const rr = i % 2 === 0 ? r : r * 0.5;
    pts.push([                              // Point2DLike:用 [x,y] 数组
      center.x + rr * Math.cos(a),
      center.y + rr * Math.sin(a)
    ]);
  }
  const poly = new vjmapext.PolylineEnt(pts, true);
  poly.color = defaults.color;
  poly.lineWidth = defaults.lineWidth;
  poly.layerId = defaults.layerId;
  return poly;
}

mapcadLayer.commandRegistry.register('STAR', DrawStarCmd, '画星形');

// 启动
mapcadLayer.executeCommand('STAR', { points: 6 });

要点:

  • 预览:PointInputOptions.callback(光标移动回调)+ setPreviewEntities
  • 事务:beginTransaction / endTransaction 让一次 Ctrl+Z 撤掉整个星形——注:execute 已对每条命令自动包事务,手动再包是无害嵌套;真正要注意的是修改类操作(scale/move 等)需先 recordModify 才入栈(store.add/delete 自动入栈,见 §六)
  • 默认值:读 drawingDefaults 填充颜色 / 线宽 / 图层
  • 选项:opts.points 让外部可配置边数

五、用户关键字支持

const r = await im.getPoint({
  message: '指定终点 [闭合(C)/撤销(U)]',
  keywords: ['C', 'U']
});

// 关键字命中:status === 'keyword',关键字文本在 r.stringResult
if (r.status === 'keyword') {
  const kw = (r.stringResult || '').toUpperCase();
  if (kw === 'C') closeShape();
  else if (kw === 'U') undoLast();
}

六、选集 + 变换

class DoubleScaleCmd {
  async main(ctx) {
    const { inputManager: im, undoManager: um } = ctx;

    // 多选是 getSelections(复数);EntityInputOptions 才有 filter
    const sel = await im.getSelections({ message: '选择要缩放为 2 倍的实体' });
    if (sel.status !== 'ok' || !sel.entities?.length) return;

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

    um.beginTransaction();  // 2x 缩放;scale(center, factor)
    sel.entities.forEach(e => { um.recordModify(e); e.scale(base.value, 2); });  // 先快照
    um.endTransaction();
  }
}
mapcadLayer.commandRegistry.register('DBLSCALE', DoubleScaleCmd);

七、异步副作用(远程查询)

命令里可以 await 后端:

class FetchAndPlaceCmd {
  async main(ctx, opts) {
    const data = await fetch(`/api/symbol/${opts.id}`).then(r => r.json());
    const pt = await ctx.inputManager.getPoint({ message: '放置点' });
    if (pt.status !== 'ok') return;

    // 构造:new BlockRefEnt(blockId?, insertionPoint?, scaleFactor?, rotation?)——第 1 参是 blockId 不是块名
    const def = ctx.blocks.itemByName(data.blockname);
    if (!def) return;
    const block = new vjmapext.BlockRefEnt(def.blockId, pt.value);
    block.setBlocksRef(ctx.blocks);   // store.add 不会自动注入 blocks 引用;也可改用 mapcadLayer.addEntity(见坑 3)
    ctx.store.add(block);
  }
}

内置 UI(createUI() 后)没有"执行中"状态提示:command:started→command:ended 期间只会高亮工具栏上同名命令按钮,命令行显示 getPoint 等的 message。await fetch 这类长耗时步骤要自己显示 loading。

八、命令选项(opts)

传进来任意对象:

mapcadLayer.executeCommand('MY_CMD', {
  color: 0xff0000,
  width: 2,
  skipPrompt: true
});

main(ctx, opts) 里 opts.skipPrompt 可用于"跳过前几步直接开始"。

九、注销命令

mapcadLayer.commandRegistry.unregister('MY_CMD');
// 或 register 同名会覆盖

十、常见坑

  1. 忘 return on cancel:用户 Esc 后下一步仍执行 → 崩溃。
  2. main 写成 static 或忘 async:registry 是 new cmdClass() 后调实例 cmd.main(ctx, opts)——static main 会抛 cmd.main is not a function 且被 try/catch 吞掉(命令看似"没反应");忘 async 则 await 写不上去。
  3. store.add vs mapcadLayer.addEntity:两者都只 emit 一次 entity:added(事件在 store.add 里发),不存在重复触发。区别是 addEntity 会先给 type==="INSERT" 的实体 setBlocksRef(this.blocks) 再委托 store.add——BlockRefEnt 建议走 addEntity,普通实体两者等价。
  4. 捕获异常吃掉:try/catch 内抛错不让用户看;用 eventBus 广播或 console。
  5. 命令里直接操作 DOM:耦合;建议 fire 事件让外部处理。

相关