唯杰地图 logo唯杰地图
VJMAP
EN

PluginContext API

vjcad/references/plugin/context-api.md 7.5 KB193 行
原文

文档:vjcad/doc/plugin/plugin-context.md、vjcad/doc/plugin/plugin-examples.md 示例:vjcad/example/plugin/04pluginmanager.js、vjcad/example/plugin/05architecture.js

PluginContext 是插件钩子的入参,封装了所有"安全注册 / 注销" API;只有它的修改会在停用(onDeactivate 之后)时被自动跟踪、清理。例外:once() 监听不跟踪、registerIcon 的图标不移除;registerSidebarPanel 的面板只在 unload 时移除(停用不移除)。

1. 命令

context.registerCommand(name, description, CommandClass, options?);
context.unregisterCommand(name);
context.executeCommand(name);             // 等价 Engine.editor.executerWithOp(name)
context.getRegisteredCommands();          // 列出本插件已注册命令

例子:

class DrawCrossCommand {
    async main() {
        const c = new Point2D(0, 0), s = 50;
        const h = new LineEnt([c.x - s, c.y], [c.x + s, c.y]);
        const v = new LineEnt([c.x, c.y - s], [c.x, c.y + s]);
        h.setDefaults(); v.setDefaults();
        Engine.addEntities([h, v], { recordUndo: true });
        Engine.pcanvas.regen();
    }
}
context.registerCommand('DRAWCROSS', '绘制十字', DrawCrossCommand, new CommandOptions());

2. 图标

context.registerIcon(name, svgString);
  • 注册到 IconRegistry 的 Commands 分类,键名统一转小写,查找也转小写 → 大小写不敏感,注册一次即可
  • 菜单项默认按 命令同名图标 渲染;Ribbon 按钮的 icon 字段填图标名(大小写随意,惯例小写命令名)
  • <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"> 是推荐模板
const ICON = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <line x1="4" y1="12" x2="20" y2="12"/>
    <line x1="12" y1="4" x2="12" y2="20"/>
</svg>`;
context.registerIcon('DRAWCROSS', ICON);   // 菜单与 Ribbon(icon: 'drawcross')共用

3. 菜单

context.addMenu(config);                    // 创建动态主菜单 { id, label, after? }(已有同 ID 动态菜单则跳过,返回 false)
context.removeMenu(menuId);

context.addMenuItem(menuId, { command, shortcut?, label?, icon?, before?, after? });
context.removeMenuItem(menuId, command);
  • 去重只针对 addMenu 建的动态菜单;removeMenu(含停用时的自动清理)会把该菜单连同其他插件挂进去的项一起删掉
  • 挂到 addMenu 建的动态菜单里的项只认 command / shortcut,before / after / label / icon 无效(这些字段只对内置菜单 tool、insert 等生效)

默认主菜单 ID:file | create | edit | view | insert | dim | property | tool | appearance | help,详见 menu-ribbon。

context.addMenuItem('tool', {
    command:  'DRAWCROSS',
    shortcut: 'Ctrl+M'
});

context.addMenu({ id: 'my-tools', label: '我的工具', after: 'tool' });
context.addMenuItem('my-tools', { command: 'DRAWCROSS' });

4. Ribbon

context.addRibbonTab(tabConfig, afterTabId?);
context.removeRibbonTab(tabId);

context.addRibbonGroup(tabId, groupConfig);
context.removeRibbonGroup(tabId, groupId);

context.addRibbonButton(tabId, groupId, buttonConfig, primaryOrMore);  // 'primary' | 'more'
context.removeRibbonButton(tabId, groupId, cmd);
// tabId 必须是已存在的标签(默认 default / tools / insert / plugins / recent,没有 'home');
// 找不到 tab(或 addRibbonButton 找不到 group)时静默 return,不报错
context.addRibbonGroup('default', {
    id: 'drawing-tools',
    label: '绘图工具',
    pinnable: true,
    displayMode: 'large',
    primaryButtons: [
        { icon: 'drawcross', cmd: 'DRAWCROSS', prompt: '绘制十字', type: 'large' }
    ],
    moreButtons: [
        { icon: 'mycmd2', cmd: 'MYCMD2', prompt: '命令2' }
    ]
});

// 添加按钮到现有组
context.addRibbonButton('default', 'draw', {
    icon: 'drawcross', cmd: 'DRAWCROSS', prompt: '绘制十字', type: 'small'
}, 'primary');

详细字段在 menu-ribbon。

5. 事件

context.on(event, handler);
context.off(event, handler);
context.once(event, handler);

event 取自 CadEvents 枚举(参见 event/types.md)。

context.on(CadEvents.EntityAdded, args => {
    console.log('实体添加', args.entity.type);
});
context.on(CadEvents.CommandStarted, args => {
    console.log('命令开始', args.commandName);
});
// ⚠️ DocumentOpened 在 vjcad@1.0.16 从不派发;打开/切换文档用 DocumentSwitched(args.previousDocument / args.currentDocument)
context.on(CadEvents.DocumentSwitched, args => {
    console.log('当前文档', args.currentDocument?.name);
});

停用时 on 注册的事件会被自动解绑,无需手动 off;但 once 不被跟踪,触发前停用仍会留着,需要时自己 Engine.eventManager.off。

6. 数据存储

context.getStorageItem<T>(key): T | null;
context.setStorageItem<T>(key, value): void;
context.removeStorageItem(key): void;

存储以插件 id 隔离,跨插件不会冲突。

interface PluginSettings { defaultSize: number; autoSave: boolean; }
const cur = context.getStorageItem<PluginSettings>('settings');
if (cur) console.log('default size', cur.defaultSize);
context.setStorageItem<PluginSettings>('settings', { defaultSize: 100, autoSave: true });

7. API 速查表

命令

方法 说明
registerCommand(name, desc, cls, opt?) 注册命令
unregisterCommand(name) 注销
executeCommand(name) 立即执行
getRegisteredCommands() 列出本插件命令

图标 / 菜单 / Ribbon

方法 说明
registerIcon(name, svg) 注册 SVG
addMenu / removeMenu 主菜单
addMenuItem / removeMenuItem 菜单项
addRibbonTab / removeRibbonTab Ribbon 标签
addRibbonGroup / removeRibbonGroup Ribbon 组
addRibbonButton / removeRibbonButton Ribbon 按钮

事件 / 存储

方法 说明
on / off / once 事件
getStorageItem / setStorageItem / removeStorageItem 持久化设置

红旗

  • onDeactivate 里未手动移除的注册项会由 context._cleanup() 自动清理(命令 / on 事件 / 菜单 / Ribbon;不含 once、图标、侧栏面板),所以手动注销可选;若手动做,按 button → group → tab、item → menu 的逆序移除可避免中间态悬挂引用
  • addMenu、addRibbonTab 已存在同 ID 时是 no-op,不会报错;多个插件可以共用 'plugins' 标签。但 addMenu 只对动态菜单去重,且创建者停用时会把别的插件挂在该菜单里的项一起删掉——共享菜单宁可挂到内置菜单(如 tool)
  • 存储是同步 API,但不要塞 MB 级 JSON,会拖慢初始化
  • registerIcon 同名会覆盖前者,且名字大小写不敏感('DRAWCROSS' 与 'drawcross' 是同一个键);想"主图标 + 备份"请使用不同 name
  • 事件 handler 内 throw 不会自动卸载插件,会在控制台 warn;自己 try/catch

参考(官方文档 MCP)

  • 文档:vjcad/doc/plugin/plugin-context.md、vjcad/doc/plugin/plugin-examples.md
  • 示例:vjcad/example/plugin/04pluginmanager.js、vjcad/example/plugin/05architecture.js