custom-command
本文回答:
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 同名会覆盖
十、常见坑
- 忘 return on cancel:用户 Esc 后下一步仍执行 → 崩溃。
- main 写成
static或忘async:registry 是new cmdClass()后调实例cmd.main(ctx, opts)——static main会抛cmd.main is not a function且被 try/catch 吞掉(命令看似"没反应");忘async则await写不上去。 - store.add vs mapcadLayer.addEntity:两者都只 emit 一次
entity:added(事件在 store.add 里发),不存在重复触发。区别是addEntity会先给type==="INSERT"的实体setBlocksRef(this.blocks)再委托 store.add——BlockRefEnt 建议走addEntity,普通实体两者等价。 - 捕获异常吃掉:try/catch 内抛错不让用户看;用 eventBus 广播或 console。
- 命令里直接操作 DOM:耦合;建议 fire 事件让外部处理。
相关
- 命令概览 → overview
- 执行入口 → execute-command
- 预览 → preview
- 快捷键 → shortcuts