PluginContext API
文档:
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