Custom Context Menus(自定义右键菜单)
Custom Context Menus - User Manual
Dragonfly Prototype Apps · Custom Context Menus...
版本 Version 1.1 · 2026-08-10
第一部分 中文手册
目录
1. 简介
2. 适用场景
3. 安装与启用
4. 使用剪贴板菜单项
5. 裁剪子体积
6. 界面
7. 配置保存在哪里
8. 工作原理,以及它不是什么
9. 以后如何增加新动作
10. 限制
1. 简介
把你自己的菜单项加到 Dragonfly 原有的右键菜单里,并在一处集中管理:添加、删除、启用、禁用。改动不需要重启 Dragonfly:视图画布上的菜单项立即生效,对象列表上的菜单项在你下次右键时生效。唯一需要重启一次的情况是某一项在插件刚安装或刚更新后第一次出现,详见「安装与启用」。
默认启用两项,分别在两个不同的右键菜单上。「复制当前视图到剪贴板」在 2D/3D 视图画布上:在画布上右键点它,你右键的那个视图就按屏幕上的实际尺寸截图并放进系统剪贴板,可直接粘贴到报告、邮件、聊天窗口或 PPT 里。「裁剪并创建子体积」在对象列表(数据属性与设置)上:右键一个图像、ROI 或 Multi-ROI,两步就能按预设尺寸切出一块。
带标记的菜单项就是本插件添加的 —— 而本手册引用的都是不带标记的名称。 本插件向 Dragonfly 自带的右键菜单里添加的每一项,名称前都会显示一个标记,默认是圆点 ●,方便你一眼看出它是 Prototype Apps 添加的,而不是 Dragonfly 原生的 —— Dragonfly 自带的菜单项永远不带标记。因此上面第一项的名称是「复制当前视图到剪贴板」,而在默认标记下,它在菜单上显示为 ● 复制当前视图到剪贴板。本手册中引用的每一个名称都不带标记。这是有意为之:标记只是显示用的前缀,你可以在「1 菜单项」页的 在 Dragonfly 菜单上标记本插件添加的项 分组里换成其他标记(•、▪、@),也可以完全关掉,所以只有不带标记的名称才对每一种设置都成立 —— 而且面板自己的菜单项列表显示的也正是这种不带标记的名称。修改标记不需要重启,两条路径只差一次右键:视图画布上的菜单项立刻改名,对象列表那一项在你下次右键时采用新标记。
源对象绝不会被修改。 裁剪会发布一个新对象,原对象保持原样。
不产生文件,也不产生对象。 不往磁盘写任何东西,Dragonfly 的对象树里也不会多出条目;截图过程需要的临时图像会立即删除。
2. 适用场景
写报告时反复截同一个视图,不想每次都走「导出视图截图」→ 选目录 → 存文件 → 再去找文件这一串。
在聊天窗口里马上把你正在看的视图发给同事或客户。
从大数据里切出一个固定尺寸的立方体 —— 在跑长时间计算之前先试算、做深度学习训练块、或者把感兴趣区域单独取出来分析 —— 而不必打开完整的裁剪工具逐个填索引范围。
把将来会陆续增加的自定义右键功能集中管理,而不是每加一个功能就多装一个插件。
3. 安装与启用
1. 打开 Prototype Apps ▸ App Store,在 Menu Manager 分组里找到 Custom Context Menus 并启用。
2. 重启 Dragonfly。插件在启动时被发现,所以必须重启。
3. 不需要任何其它设置。引擎随 Dragonfly 启动,自己把已启用的菜单项加上去,不必先打开面板。
4. 需要增删或关闭菜单项时,面板在 Prototype Apps ▸ Custom Context Menus...。
5. 裁剪并创建子体积会在这次重启之后出现在对象列表的右键菜单里。Dragonfly 只在启动时读取插件的菜单,所以第一次出现必须重启;之后再勾选或取消勾选,和其它菜单项一样立即生效。
无需安装、无需联网、不需要 GPU、不需要虚拟环境 —— 纯进程内 PyQt6。
4. 使用剪贴板菜单项
1. 在 2D 或 3D 视图画布上任意位置右键。
2. 点菜单最下方、分隔线下面的 复制当前视图到剪贴板 —— 除非你把标记关掉了,否则它的名称前会带一个标记。
3. 在你正在写的东西里 Ctrl+V 粘贴。
截出来的图就是该视图在屏幕上的实际像素尺寸。在画布上右键会把那个视图变成当前视图,这正是多个视图并存时菜单项能知道你指的是哪一个的原因。
⚠ 如果截出来的图看起来是空白的,「日志」页会直接告诉你,而不是等你粘贴之后才发现。刚创建或正在渲染中的视图可能截出纯色,重新右键复制一次即可。
5. 裁剪子体积
在对象列表(数据属性与设置)中选中一个图像、ROI 或 Multi-ROI,右键,选裁剪并创建子体积 —— 同样地,除非你把标记关掉了,否则它的名称前也会带一个标记。它和 Dragonfly 自带的裁剪功能一起放在 Modify and Transform 下面。
1. 选尺寸:300 x 300 x 300、400 x 400 x 400、500 x 500 x 500,或选自定义逐轴填写。
2. 中心保持不动就是围绕源对象自身的中心裁剪;也可以填 X / Y / Z 偏移。偏移的单位是体素:X 取 +1 表示这一块沿 X 正方向平移一个体素。
3. 看将要创建的对象那一行。它随你的输入实时更新,给出最终尺寸、体素总数、大致占用,以及是否有某个轴是被源对象边界决定的。
4. 点创建子体积。新对象会出现在对象列表里,名称形如 <源名称> crop 500x500x450。
裁剪盒放不下时会怎样
以源对象自身的边界作为新对象的边界,不做任何填充。结果是「你要的」与「实际存在的」两者的交集,因此新对象里只有真实数据。
源尺寸 | 请求尺寸 | 偏移 | 结果 |
1000 x 1000 x 450 | 500 x 500 x 500 | 0, 0, 0 | 500 x 500 x 450(Z 轴被限制) |
300 x 200 x 100 | 500 x 500 x 500 | 0, 0, 0 | 300 x 200 x 100(整个源对象) |
1000 x 1000 x 450 | 300 x 300 x 300 | 400, 0, 0 | 250 x 300 x 300(X 轴被限制) |
对话框在你点「创建」之前就说明这一点并指出是哪个轴,所以结果变小是你读到的,而不是事后才发现的。
⚠ 如果偏移大到让裁剪盒完全落在源对象之外,裁剪会被拒绝,并告诉你是哪个轴、以及该轴的有效偏移范围。它不会被悄悄压成一薄片 —— 在一个 1000 体素的轴上,2250..2749 这样的盒子若只做简单夹取,会得到一个 1 体素的对象,而它看起来就像裁剪成功了。
哪些信息被保留
体素尺寸、坐标轴方向和世界坐标原点全部沿用源对象,因此子体积就落在源对象中对应的那个位置上,测量结果可以互相比较。不做任何重采样或插值:裁剪用的是 Dragonfly 自己的原生子集操作,和内置的 Crop... 同一条路径。
Multi-ROI 的标签名称、颜色和标量值都会保留。4D 数据的所有时间步一起裁剪,对话框会显示共有多少个时间步。
为什么一次只能处理一个对象
该菜单项只在恰好选中一个图像、ROI 或 Multi-ROI 时出现,其它情况都不出现 —— 网格不出现、图(Graph)不出现、标注不出现,同时选中多个也不出现。对话框只预览一个结果,而两个对象很少具有相同尺寸,同一个裁剪盒对每个对象的含义并不相同,预览最多只能对其中一个成立。Dragonfly 自带的 Crop... 也是同样的做法。当选中多个可裁剪对象时,原因会写进 Dragonfly 的 Python 日志。
6. 界面
1 菜单项 —— 每行一个菜单项:启用勾选框、名称、它执行的动作、它出现在哪个菜单上、出现条件、以及它是内置的还是你添加的。勾选与取消勾选即时生效。「出现条件」这一列对裁剪那一项尤其重要:它只在选中一个图像、ROI 或 Multi-ROI 时才出现,没有这一列的话,一个工作正常的菜单项看起来会像是坏了。添加菜单项… 新建,删除所选 删除,立即应用 重新加入全部菜单项,恢复默认 回到只有两个内置项的状态。
添加菜单项… 只提供那些可以由你指定目标菜单的动作。裁剪那一项不在其中:它出现在哪里由 Dragonfly 决定,因此再复制一份指向别处也不可能显示出来。
同一页上还有 在 Dragonfly 菜单上标记本插件添加的项。「名称前的标记」提供圆点(默认)、小圆点、小方块、@ 符号,以及「不加标记」用于彻底关闭;下面一行按菜单上的实际样子显示一个示例,因此你是照着「将会看到的样子」来选的。修改对视图画布上的菜单项立即生效,对象列表那一项在你下次右键时生效;点「恢复默认」会把标记和菜单项一起恢复为默认。无论你选哪一种,上面的菜单项列表都保持不带标记 —— 本面板里的每一项本来就都是本插件添加的,在这里加标记提供不了任何信息。
2 已发现的菜单 —— 当前 Dragonfly 中所有带名字的菜单,含 objectName、实例数、条目数、其中属于本插件的条目数,以及开头几项内容。添加菜单项时就在这里找目标菜单的 objectName。这份清单来自正在运行的程序,因此对你用的这个 Dragonfly 版本一定是准的,而不是插件写死的一张表。Dragonfly 尚未构建出来的菜单不会列出:先打开界面上对应的部分,再点「刷新」。
3 日志 —— 每次点击的结果:复制了哪个视图、多大尺寸,或者为什么没成功。菜单项的添加、删除与重新加入也记录在这里。
7. 配置保存在哪里
%LOCALAPPDATA%\DragonflyPrototypeLabs\CustomContextMenus\items.json,原子写入。它位于插件目录之外、也在 App Store 的软件包存储之外,因此重装插件、App Store 更新、换 Dragonfly 版本都不会丢,并且所有已安装的 Dragonfly 版本共用同一份配置。
文件损坏或被手工改坏时,功能不会因此消失:菜单项会回退到出厂默认。老版本写下的文件会自动补上新增的内置菜单项,而你主动关掉的菜单项则保持关闭状态。
8. 工作原理,以及它不是什么
本插件加的是 Dragonfly 自己构建的右键菜单,并且针对不同的菜单使用 Dragonfly 两套机制中真正合适的那一套。这两套机制不能互换。
视图画布是原生的。它的菜单由 Dragonfly 的 C++ 代码构建 —— Export Screenshot of View 这行字在整个安装目录的任何 Python 文件里都找不到 —— 而它提供的“选择”并不是 Python 上下文菜单项会接受的东西:已安装的 182 个子类全都不接受,而且画布菜单无论是否选中对象都完全一样。因此用那种方式写的菜单项永远不会出现在那里。
但它终究是一个和本插件同处一个进程的普通 Qt 菜单,所以可以直接往里加一项。有两个实测细节决定了具体做法:Dragonfly 每次右键都新建一个菜单对象并保留旧的,而且不发 Qt 的 aboutToShow 信号。因此插件在每个菜单被创建的那一刻就发现它并加入菜单项 —— 这也正是为什么菜单项不需要重启就出现,以及为什么 Dragonfly 重建菜单后该项会立刻回来。
对象列表恰好是相反的情况。它的菜单由 Python 构建,内容来自你选中的对象,这正是 Dragonfly 官方的上下文菜单通道。所以裁剪那一项是用官方方式注册的,由插件自己在每次右键时判断当前选择是否为一个图像、ROI 或 Multi-ROI。这里既没有注入、也没有监视、更没有轮询,该菜单项也绝不可能出现在不该出现的菜单上。由于 Dragonfly 在启动时读取插件菜单,这一项第一次出现需要重启一次;之后开关它立即生效。
不删除、不修改任何已有条目。只添加一条分隔线和你启用的菜单项,且只加到相关的菜单上。重启 Dragonfly 会清除插件在内存里所做的一切;把菜单项带回来的是那个配置文件。
9. 以后如何增加新动作
剪贴板这一项只是开头,插件的结构让新增一个动作成为很小的局部改动:在 custom_menus_core.py 的动作表里加一条,给出中英文名称、一句说明和要执行的函数。它随后就会出现在 添加菜单项… 里、可加到任何已发现的菜单上,面板、持久化和菜单机制都不需要改。
加到对象列表上的菜单项要多做一点,因为它出现在哪里是由 Dragonfly 决定而不是本插件决定:还需要向 Dragonfly 自己的上下文菜单机制注册,正是这一步让它能够精确声明想要的选择条件。面板对它的列出与开关方式和其它菜单项完全一样。
10. 限制
只能加到 Dragonfly 已经创建出来的菜单上 —— 这正是「已发现的菜单」页存在的原因,也是它提示你先打开界面上对应部分的原因。
本插件不会删除或重排 Dragonfly 自己的菜单项,也不改变它们的行为。
在视图画布上,菜单项添加在菜单末尾;本版本不支持指定它在该菜单内的位置。裁剪那一项的位置由 Dragonfly 决定,它会被放到其它 Modify and Transform 工具旁边。
裁剪一次只处理一个对象,并且只沿体素坐标轴进行 —— 这里没有旋转裁剪、没有任意形状裁剪,也没有重采样。需要精确填写索引范围时用 Dragonfly 自带的 Crop...,需要旋转盒子时用它基于形状的提取功能。
插件首次安装或更新后,裁剪那一项需要重启 Dragonfly 一次,因为插件的菜单是在启动时读取的。
Part II English Manual
Contents
1. Introduction
2. Use cases
3. Installation & enabling
4. Using the clipboard entry
5. Cropping a sub-volume
6. Interface
7. Where the configuration lives
8. How it works, and what it is not
9. Adding more actions later
10. Limitations
1. Introduction
Adds your own entries to Dragonfly's own context menus and manages them from one place: add, remove, enable, disable. Changes need no Dragonfly restart: on the view canvas they take effect immediately, and on the object list at your next right-click. The one thing that does need a restart is an entry appearing for the very first time, right after the plugin is installed or updated — see Installation & enabling.
Two entries ship enabled, on two different menus. Copy current view to clipboard is on the 2D/3D view canvas: right-click a canvas, click it, and a screenshot of the view you right-clicked - at its own size on screen - is on the system clipboard, ready to paste into a report, an email, a chat or a slide. Crop and create a sub-volume is on the object list (Data Properties and Settings): right-click one image, ROI or Multi-ROI and cut a preset-sized block out of it in two clicks.
A marker shows which entries are ours - and this manual quotes names without it. Every entry this plugin adds is displayed with one glyph in front of its name, ● by default, so you can see at a glance that Prototype Apps added it and Dragonfly did not; Dragonfly's own entries never carry one. So the first entry above is named Copy current view to clipboard, and with the default marker it reads ● Copy current view to clipboard on the menu. Every label quoted in this manual is the name, without the marker. That is deliberate: the marker is a display prefix only, and you can change it (•, ▪, @) or switch it off completely in the Mark our entries on Dragonfly's menus group of the 1 Entries tab, so the plain name is the one form that is correct whichever setting you chose - and it is also what the panel's own list of entries shows. Changing it needs no restart, and the two routes differ by one right-click: the view-canvas entries are re-labelled immediately, and the object-list entry picks the new marker up the next time you right-click.
The source is never modified. Cropping publishes a NEW object and leaves the original exactly as it was.
No file, no object. Nothing is written to disk and nothing appears in Dragonfly's object tree. The temporary image the capture needs is deleted immediately.
2. Use cases
Grabbing the same view over and over while writing a report, without going through Export Screenshot of View, choosing a folder, saving a file and then finding it again.
Sending a colleague or a customer the view you are looking at, right now, in a chat window.
Cutting a fixed-size cube out of a large dataset - a trial run before committing to a long computation, a training block for deep learning, or a region of interest to analyse on its own - without opening a full cropping tool and typing index ranges.
Keeping the custom right-click features you will keep adding managed in one place, instead of installing a separate plugin for each one.
3. Installation & enabling
1. Open Prototype Apps ▸ App Store, find Custom Context Menus in the Menu Manager group, and enable it.
2. Restart Dragonfly. Plugins are discovered at startup, so a restart is required.
3. Nothing else is needed. The engine starts with Dragonfly and adds the enabled entries by itself; the panel does not have to be opened first.
4. The panel is at Prototype Apps ▸ Custom Context Menus... if you want to add, remove or turn entries off.
5. Crop and create a sub-volume appears in the object list's right-click menu after that restart. Dragonfly reads a plugin's menus only when it starts, so the very first appearance needs the restart; ticking and unticking the entry afterwards is immediate, like every other entry.
No install, no internet, no GPU, no virtual environment — pure in-process PyQt6.
4. Using the clipboard entry
1. Right-click anywhere on a 2D or 3D view canvas.
2. Click Copy current view to clipboard - shown with the marker in front of it, unless you switched the marker off - at the bottom of the menu below a separator.
3. Paste with Ctrl+V into whatever you are writing.
The captured image is the view's actual size on screen, in device pixels. Right-clicking a view canvas makes that view the current view, which is how the entry knows which of several views you meant.
⚠ If the captured image looks blank the Log tab says so instead of leaving you to discover it after pasting. A view that has just been created or is mid-render can capture flat; right-click and copy again.
5. Cropping a sub-volume
Select one image, ROI or Multi-ROI in the object list (Data Properties and Settings), right-click it, and choose Crop and create a sub-volume - again shown with the marker in front of it, unless you switched the marker off. It sits with Dragonfly's own crop entries under Modify and Transform.
1. Pick a size: 300 x 300 x 300, 400 x 400 x 400, 500 x 500 x 500, or Custom for any size per axis.
2. Leave the centre alone to crop around the source's own centre, or set the X / Y / Z offsets. The offsets are in voxels: +1 on X moves the block one voxel towards higher X.
3. Read the What will be created line. It updates as you type and gives the exact extent, the voxel count, the approximate size, and whether the source boundary decided any axis.
4. Press Create the sub-volume. The new object appears in the object list, named <source> crop 500x500x450.
What happens when the box does not fit
The source's own boundary becomes the boundary of the new object, and nothing is padded. The result is the overlap between what you asked for and what exists, so it contains only real data.
Source | Requested | Offset | Result |
1000 x 1000 x 450 | 500 x 500 x 500 | 0, 0, 0 | 500 x 500 x 450 (clamped on Z) |
300 x 200 x 100 | 500 x 500 x 500 | 0, 0, 0 | 300 x 200 x 100 (the whole source) |
1000 x 1000 x 450 | 300 x 300 x 300 | 400, 0, 0 | 250 x 300 x 300 (clamped on X) |
The dialog states this before you commit, naming the axis, so a smaller result is something you read rather than discover afterwards.
⚠ If an offset is large enough to push the box completely outside the source, the crop is refused and the dialog says which axis and which offsets would work. It is not quietly clamped down to a sliver - a box at 2250..2749 in a 1000-voxel axis would otherwise produce a 1-voxel object that looks like a successful crop.
What is preserved
Voxel size, axis directions and the world origin are inherited from the source, so the sub-volume sits exactly where that part of the source sat and measurements stay comparable. Nothing is resampled or interpolated: the crop is Dragonfly's own native subset operation, the same one the built-in Crop... uses.
A Multi-ROI keeps its label names, colours and scalar values. A 4-D dataset is cropped on every time step, and the dialog says how many there are.
Why only one object at a time
The entry appears for exactly one image, ROI or Multi-ROI, and for nothing else - not for a mesh, not for a graph, not for an annotation, and not for a multi-selection. The dialog previews one result, and two objects rarely have the same extent, so the same box would mean a different crop for each of them and the preview would be right for at most one. Dragonfly's own Crop... works the same way. When several objects that could be cropped are selected, the reason is written to Dragonfly's Python log.
6. Interface
1 Entries - one row per entry: enabled tick box, its label, the action it performs, the menu it appears on, when it is shown, and whether it is built in or one you added. Tick and untick to turn an entry on and off, live. The Shown when column matters for the crop entry: it appears only for one selected image, ROI or Multi-ROI, so without that column a correctly working entry could look broken. Add entry... creates a new one, Remove selected deletes it, Apply now re-asserts everything, Reset to defaults returns to the two built-in entries.
Add entry... offers only the actions that can be aimed at a menu of your choosing. The crop entry is not among them: Dragonfly decides where it appears, so a second copy pointed somewhere else could not show up.
The same tab carries Mark our entries on Dragonfly's menus. Marker in front of the name offers the dot (the default), the bullet, the small square, the at sign, and no marker to turn the feature off; the line under it shows an entry exactly as the menu will render it, so you choose by what you will actually see. A change is immediate for the view-canvas entries, and the object-list entry picks it up the next time you right-click. Reset to defaults restores the marker along with the entries. The list of entries above stays plain whatever you choose - every entry in this panel is ours already, so a marker there would tell you nothing.
2 Discovered menus — every named menu alive in the running Dragonfly, with its objectName, how many instances exist, how many items it has, how many of them are ours, and its first few entries. This is where you find the objectName to aim a new entry at. The list comes from the running application, so it is right for the Dragonfly version you are using rather than a list fixed when the plugin was written. A menu Dragonfly has not built yet is not listed: open that part of the interface once, then press Refresh.
3 Log — the result of every click: which view was copied and at what size, or why it could not be. Also records entries being added, removed and re-asserted.
7. Where the configuration lives
%LOCALAPPDATA%\DragonflyPrototypeLabs\CustomContextMenus\items.json, written atomically. It sits outside the plugin folder and outside the App Store's package store, so it survives a plugin reinstall, an App Store update and a new Dragonfly version, and every installed Dragonfly version shares the same configuration.
A corrupt or hand-edited file never leaves you without the feature: the entries fall back to the shipped default. A file written by an older build gains any newly shipped built-in entry, while an entry you deliberately turned off stays off.
8. How it works, and what it is not
This plugin adds to the context menus Dragonfly itself builds, and it uses whichever of Dragonfly's two mechanisms actually fits the menu in question. They are not interchangeable.
The view canvas is native. Its menu is built by Dragonfly's C++ code - the text Export Screenshot of View appears in no Python file in the whole installation - and the selection it offers is not something a Python contextual menu item will accept: all 182 installed subclasses decline it, and the canvas menu is identical whether or not an object is selected. So nothing written that way can appear there.
It is, however, an ordinary Qt menu living in the same process as this plugin, so an entry can simply be added to it. Two measured details decide how: Dragonfly creates a new menu object for every right-click and keeps the old ones, and it does not emit Qt's aboutToShow on them. So the plugin notices each menu as it is created and adds the entry then — which is why an entry appears without a restart, and why a menu Dragonfly rebuilds gets the entry straight back.
The object list is the opposite case. Its menu is built in Python and filled from the objects you have selected, which is exactly Dragonfly's supported contextual-menu channel. So the crop entry is registered the official way, with the plugin itself deciding - each time you right-click - whether the selection is one image, ROI or Multi-ROI. There is no injection, no monitoring and no polling involved, and the entry can never land on a menu it was not meant for. Because Dragonfly reads a plugin's menus at startup, that entry needs one restart the first time; after that, turning it on and off is immediate.
Nothing existing is removed or altered. Only a separator and the entries you enabled are added, and only to the menus concerned. A Dragonfly restart clears everything the plugin did in memory; the configuration file is what brings the entries back.
9. Adding more actions later
The clipboard entry is the first of a growing set, and the plugin is built so a new action is a small, local change: one entry in the action table in custom_menus_core.py giving its English and Chinese label, a one-line description and the function to run. It then appears in Add entry... for every discovered menu, with no change to the panel, the persistence or the menu machinery.
An entry on the object list is a little more than that, because Dragonfly - not this plugin - decides where it appears: it also needs registering with Dragonfly's own contextual-menu mechanism, which is what allows it to state the exact selection it wants. The panel still lists and toggles it like any other entry.
10. Limitations
Entries can only be added to menus Dragonfly has already created, which is why the Discovered menus tab exists and why it asks you to open the relevant part of the interface first.
The plugin does not remove or reorder Dragonfly's own entries, and does not change what they do.
On the view canvas an entry is added at the end of the menu; placement inside that menu is not configurable in this version. The crop entry's position is decided by Dragonfly, which puts it with the other Modify and Transform tools.
Cropping handles one object per run and only along the voxel axes - there is no rotated or arbitrary-shape crop here, and no resampling. Use Dragonfly's own Crop... when you need to type exact index ranges, or its shape-based extraction for a rotated box.
The crop entry needs one Dragonfly restart after the plugin is first installed or updated, because a plugin's menus are read at startup.