唯杰地图 logo唯杰地图
VJMAP
EN

命令系统与用户输入

vjcad/references/autodraw/commands-input.md 10.9 KB382 行
原文

自定义命令的创建与注册、用户输入交互、事件系统。


基础命令定义

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 虽在枚举中,但不派发。)