命令系统与用户输入
自定义命令的创建与注册、用户输入交互、事件系统。
基础命令定义
const {
CommandDefinition, CommandRegistry, CommandOptions,
writeMessage, Engine
} = vjcad;
class HelloCommand {
async main() {
writeMessage("<br/>Hello WebCAD!");
Engine.zoomExtents();
}
}
const options = new CommandOptions();
options.useAutoComplete = true;
const cmdDef = new CommandDefinition(
'HELLO', // 命令名(大写)
'简单示例命令', // 描述
HelloCommand, // 命令类
options // 选项(可选)
);
CommandRegistry.regist(cmdDef);
// 执行命令
await Engine.editor.executerWithOp('HELLO');
命令类标准模板
class MyCommand {
constructor() {
// 初始化状态
}
async main() {
Engine.undoManager.start_undoMark();
try {
await this.doWork();
} catch (error) {
writeMessage(`<br/>错误: ${error.message}`);
} finally {
Engine.undoManager.end_undoMark();
Engine.clearPreview();
}
}
async doWork() {
// 实际命令逻辑
}
}
命令注册与管理
// 批量注册
const commands = [
{ name: 'CMD1', desc: '命令1', cls: Cmd1Class },
{ name: 'CMD2', desc: '命令2', cls: Cmd2Class },
];
commands.forEach(cmd => {
CommandRegistry.regist(
new CommandDefinition(cmd.name, cmd.desc, cmd.cls)
);
});
// 查询
const cmdInfo = CommandRegistry.item('CMD1');
// 注销
CommandRegistry.unregist('CMD1');
用户输入函数
getPoint — 获取点
const { PointInputOptions, InputStatusEnum, getPoint } = vjcad;
const options = new PointInputOptions("指定点位置:");
const result = await getPoint(options);
if (result.status === InputStatusEnum.OK) {
const point = result.value; // Point2D { x, y }
}
带橡皮筋线:
const opt = new PointInputOptions("指定终点:");
opt.useBasePoint = true;
opt.basePoint = startPoint;
const result = await getPoint(opt);
带关键字选项:
const opt = new PointInputOptions("指定点 [直线(L)/圆(C)/退出(E)]:");
opt.keywords = ["L", "C", "E"];
const result = await getPoint(opt);
switch (result.status) {
case InputStatusEnum.OK:
const point = result.value; // 用户点击了坐标点
break;
case InputStatusEnum.Keyword:
const keyword = result.stringResult.toUpperCase();
break;
case InputStatusEnum.Cancel: // ESC(不抛异常)
break;
case InputStatusEnum.None: // 空输入回车/空格(getPoint 不返回 EnterOrSpace)
break;
}
自定义预览:
const opt = new PointInputOptions("指定终点:");
opt.useBasePoint = true;
opt.basePoint = startPoint;
opt.hideRubberBand = true;
opt.callback = (canvasPt) => {
const worldPt = Engine.canvasToWcs(canvasPt);
const preview = new LineEnt([startPoint.x, startPoint.y], [worldPt.x, worldPt.y]);
preview.setDefaults();
Engine.clearPreview();
Engine.drawPreviewEntities([preview]);
};
const result = await getPoint(opt);
Engine.clearPreview();
getSelections — 获取选择集
const { SelectionInputOptions, InputStatusEnum, getSelections } = vjcad;
const options = new SelectionInputOptions(); // 无参构造:该类只有 useOsnap 等字段,没有提示语参数
const result = await getSelections(options);
if (result.status === InputStatusEnum.OK) {
const entities = result.value; // 实体数组
}
getReal — 获取实数
const { RealInputOptions, InputStatusEnum, getReal } = vjcad;
const options = new RealInputOptions("输入半径:");
options.defaultValue = 30;
const result = await getReal(options);
if (result.status === InputStatusEnum.OK) {
const radius = result.value;
}
getInteger — 获取整数
const { IntegerInputOptions, InputStatusEnum, getInteger } = vjcad;
const options = new IntegerInputOptions("输入边数:");
options.defaultValue = 6;
const result = await getInteger(options);
getString — 获取字符串
const { StringInputOptions, InputStatusEnum, getString } = vjcad;
const options = new StringInputOptions("输入图层名称:");
// StringInputOptions 无 defaultValue(字段只有 message/keywords/allowEmpty/defaultKeyWord)
const result = await getString(options);
// 空输入回车返回 status === InputStatusEnum.None,默认值自己兜底;注意空格也会结束输入
const layerName = result.status === InputStatusEnum.OK ? result.value : "新图层";
getCorner — 获取矩形对角点
const { CornerInputOptions, getCorner } = vjcad;
const options = new CornerInputOptions("指定对角点:");
options.basePoint = startPoint;
const result = await getCorner(options);
InputStatusEnum 状态
| 状态 | 说明 |
|---|---|
OK |
成功获取输入 |
Cancel |
用户按 ESC(输入函数返回该状态,不抛异常) |
None |
空输入时按回车/空格(getPoint/getString/无默认值的 getReal 等;getPoint 设了 defaultKeyword 时改返回 Keyword) |
EnterOrSpace |
回车/空格——只由 getEntity 返回,getPoint 判"完成"要用 None |
Keyword |
用户输入关键字 |
状态机命令模式
处理复杂的多步骤命令:
const {
PointInputOptions, InputStatusEnum,
getPoint, PolylineEnt, Engine, writeMessage
} = vjcad;
class DrawPolylineCommand {
constructor() {
this.step = 1;
this.points = [];
this.isClosed = false;
}
async main() {
Engine.undoManager.start_undoMark();
try {
while (this.step > 0) {
switch (this.step) {
case 1: await this.getFirstPoint(); break;
case 2: await this.getNextPoints(); break;
case 3: this.finish(); break;
}
}
} finally {
Engine.undoManager.end_undoMark();
Engine.clearPreview();
}
}
async getFirstPoint() {
const opt = new PointInputOptions("指定起点:");
const result = await getPoint(opt);
if (result.status === InputStatusEnum.OK) {
this.points.push(result.value);
this.step = 2;
} else {
this.step = 0;
}
}
async getNextPoints() {
const opt = new PointInputOptions("指定下一点 [闭合(C)/撤销(U)] <完成>:");
opt.keywords = ["C", "U"];
if (this.points.length > 0) {
opt.useBasePoint = true;
opt.basePoint = this.points[this.points.length - 1];
}
const result = await getPoint(opt);
if (result.status === InputStatusEnum.OK) {
this.points.push(result.value);
} else if (result.status === InputStatusEnum.Keyword) {
const kw = result.stringResult.toUpperCase();
if (kw === "C") { this.isClosed = true; this.step = 3; }
else if (kw === "U" && this.points.length > 1) { this.points.pop(); }
} else if (result.status === InputStatusEnum.None) { // 空输入回车/空格 = 完成
this.step = 3;
} else {
this.step = 0;
}
}
finish() {
if (this.points.length >= 2) {
const pline = new PolylineEnt();
pline.setPoints(this.points.map(p => [p.x, p.y]));
pline.isClosed = this.isClosed;
pline.setDefaults();
Engine.addEntities(pline);
}
this.step = 0;
}
}
事件系统
注册事件监听
const { CadEventManager, CadEvents, Engine } = vjcad;
const events = Engine.eventManager;
events.on(CadEvents.EntityAdded, (args) => {
console.log('实体已添加:', args.entity.type);
});
events.once(CadEvents.DocumentSwitched, (args) => { // DocumentOpened 当前版本不派发
console.log('切换到文档:', args.currentDocument.name);
});
取消订阅
const handler = (args) => { console.log(args); };
events.on(CadEvents.EntityAdded, handler);
events.off(CadEvents.EntityAdded, handler); // 移除指定监听器
events.offAll(CadEvents.EntityAdded); // 移除该事件所有监听器
events.offAll(); // 移除所有监听器
事件管理器方法
| 方法 | 说明 |
|---|---|
on(event, handler) |
注册监听器 |
once(event, handler) |
一次性监听器 |
off(event, handler) |
移除指定监听器 |
offAll(event?) |
移除所有监听器 |
hasListeners(event) |
检查是否有监听器 |
suspendEvents() |
暂停事件触发 |
resumeEvents() |
恢复事件触发 |
常用事件
| 事件 | 说明 |
|---|---|
EntityAdded / EntitiesAdded |
实体添加 |
EntityModified |
实体修改 |
EntitiesErased |
实体删除 |
SelectionChanged |
选择集变化 |
CommandStarted / CommandEnded |
命令开始/结束(ESC 取消也只发 CommandEnded) |
DocumentCreated / DocumentSwitched / DocumentModified |
文档新建 / 切换 / 首次变脏 |
LayerAdded / CurrentLayerChanged |
图层变化 |
ViewZoomed / ViewRegenerated |
视图变化 |
⚠️
DocumentOpening/Opened/Closing/Closed/Saving/Saved、EntityErasing/EntityErased、CommandCancelled、LayerModified、ViewPanned、Block*以及协作/缓存/分支/Patch/冲突类事件在当前版本没有任何派发点,订阅永不触发。全表见 event/overview。
可取消事件(以 -ing 结尾)
// 删除只派发批量版 EntitiesErasing(单数 EntityErasing 不派发)
events.on(CadEvents.EntitiesErasing, (args) => {
if (args.entities.some(e => e.layer === "保护图层")) {
args.cancel = true;
args.cancelReason = "保护图层上的实体不允许删除";
}
});
// 阻止保存:DocumentSaving 不派发,改在命令层拦截
events.on(CadEvents.CommandStarting, (args) => {
if (args.commandName === "QSAVE") args.cancel = true;
});
实际会派发的可取消事件:DocumentCreating、EntityAdding、EntitiesAdding、EntitiesErasing、CommandStarting、LayerAdding、LayerDeleting、ContextMenuOpening(Plugin*ing 的参数类型不带 cancel,不可取消)。(DocumentOpening/Closing/Saving、EntityErasing、BlockAdding/Deleting 虽在枚举中,但不派发。)