3D Slicer 架构分析
分析对象 :
D:\Slicer,分支5.8生成日期 :2026-07-28
路径约定 :文中所有代码路径均相对于 Slicer 源码根目录
图表说明 :使用 Mermaid,需在支持 Mermaid 的 Markdown 阅读器中查看
(VSCode + Markdown Preview Mermaid Support / Typora / GitHub / Obsidian)
目录
第一部分:整体框架
- 总体分层
- [SuperBuild ------ 依赖构建层](#SuperBuild —— 依赖构建层)
- [Libs ------ 数据模型与基础库](#Libs —— 数据模型与基础库)
- [Base ------ 应用框架层](#Base —— 应用框架层)
- [Modules ------ 四类模块机制](#Modules —— 四类模块机制)
- [Applications 与扩展](#Applications 与扩展)
- 运行时数据流
- 第一部分小结
第二部分:扩展机制底层基础
- 核心结论
- 支柱一:模块搜索路径
- [支柱二:CTK AppLauncher](#支柱二:CTK AppLauncher)
- [支柱三:版本与 ABI 绑定](#支柱三:版本与 ABI 绑定)
- [支柱四:CMake 构建体系](#支柱四:CMake 构建体系)
- [扩展间依赖:find_package 复用](#扩展间依赖:find_package 复用)
- 支柱五:元数据与服务器
- 生命周期状态机
- 完整安装流程(运行时)
- 第二部分小结
附录
- [A. 关键文件速查](#A. 关键文件速查)
- [B. 关键数字](#B. 关键数字)
- [C. 可继续深入的方向](#C. 可继续深入的方向)
全局概览
两句话概括
架构主线
Slicer 是"以 MRML 场景为唯一真相源、以事件驱动同步视图、以四类可插拔模块扩展功能"的分层架构。
贯穿始终的两条设计主线:Data / Display / Storage 三节点分离 ,以及 Logic / GUI 分离。
扩展机制主线
扩展不是独立的插件框架,它复用的就是模块加载机制。
Slicer 只是把"模块搜索路径"暴露为一个可配置变量(
Modules/AdditionalPaths),剩下的全是打包、版本绑定和分发的工程问题。
两部分的关系
#mermaid-svg-kwx6UiwWVXT5Y2IB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kwx6UiwWVXT5Y2IB .error-icon{fill:#552222;}#mermaid-svg-kwx6UiwWVXT5Y2IB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kwx6UiwWVXT5Y2IB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .marker.cross{stroke:#333333;}#mermaid-svg-kwx6UiwWVXT5Y2IB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kwx6UiwWVXT5Y2IB p{margin:0;}#mermaid-svg-kwx6UiwWVXT5Y2IB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .cluster-label text{fill:#333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .cluster-label span{color:#333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .cluster-label span p{background-color:transparent;}#mermaid-svg-kwx6UiwWVXT5Y2IB .label text,#mermaid-svg-kwx6UiwWVXT5Y2IB span{fill:#333;color:#333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .node rect,#mermaid-svg-kwx6UiwWVXT5Y2IB .node circle,#mermaid-svg-kwx6UiwWVXT5Y2IB .node ellipse,#mermaid-svg-kwx6UiwWVXT5Y2IB .node polygon,#mermaid-svg-kwx6UiwWVXT5Y2IB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kwx6UiwWVXT5Y2IB .rough-node .label text,#mermaid-svg-kwx6UiwWVXT5Y2IB .node .label text,#mermaid-svg-kwx6UiwWVXT5Y2IB .image-shape .label,#mermaid-svg-kwx6UiwWVXT5Y2IB .icon-shape .label{text-anchor:middle;}#mermaid-svg-kwx6UiwWVXT5Y2IB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kwx6UiwWVXT5Y2IB .rough-node .label,#mermaid-svg-kwx6UiwWVXT5Y2IB .node .label,#mermaid-svg-kwx6UiwWVXT5Y2IB .image-shape .label,#mermaid-svg-kwx6UiwWVXT5Y2IB .icon-shape .label{text-align:center;}#mermaid-svg-kwx6UiwWVXT5Y2IB .node.clickable{cursor:pointer;}#mermaid-svg-kwx6UiwWVXT5Y2IB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .arrowheadPath{fill:#333333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kwx6UiwWVXT5Y2IB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kwx6UiwWVXT5Y2IB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kwx6UiwWVXT5Y2IB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kwx6UiwWVXT5Y2IB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kwx6UiwWVXT5Y2IB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kwx6UiwWVXT5Y2IB .cluster text{fill:#333;}#mermaid-svg-kwx6UiwWVXT5Y2IB .cluster span{color:#333;}#mermaid-svg-kwx6UiwWVXT5Y2IB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kwx6UiwWVXT5Y2IB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kwx6UiwWVXT5Y2IB rect.text{fill:none;stroke-width:0;}#mermaid-svg-kwx6UiwWVXT5Y2IB .icon-shape,#mermaid-svg-kwx6UiwWVXT5Y2IB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kwx6UiwWVXT5Y2IB .icon-shape p,#mermaid-svg-kwx6UiwWVXT5Y2IB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kwx6UiwWVXT5Y2IB .icon-shape .label rect,#mermaid-svg-kwx6UiwWVXT5Y2IB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kwx6UiwWVXT5Y2IB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kwx6UiwWVXT5Y2IB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kwx6UiwWVXT5Y2IB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第二部分:扩展机制
第一部分:整体框架
复用
复用
Applications/SlicerApp
Modules
Core / Loadable / Scripted / CLI
Base
QTApp / QTGUI / QTCore / QTCLI / Logic
Libs
MRML / vtkITK / vtkTeem / vtkSegmentationCore
SuperBuild
VTK / ITK / CTK / Qt / Python / DCMTK
扩展 Extension
模块搜索路径
Modules/AdditionalPaths
CTK AppLauncher
环境注入
revision × OS × arch
ABI 绑定
SlicerConfig.cmake
构建体系
.s4ext / JSON catalog
元数据分发
第一部分:整体框架
1. 总体分层
#mermaid-svg-ArWq6zTRx3raG3VA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ArWq6zTRx3raG3VA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ArWq6zTRx3raG3VA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ArWq6zTRx3raG3VA .error-icon{fill:#552222;}#mermaid-svg-ArWq6zTRx3raG3VA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ArWq6zTRx3raG3VA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ArWq6zTRx3raG3VA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ArWq6zTRx3raG3VA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ArWq6zTRx3raG3VA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ArWq6zTRx3raG3VA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ArWq6zTRx3raG3VA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ArWq6zTRx3raG3VA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ArWq6zTRx3raG3VA .marker.cross{stroke:#333333;}#mermaid-svg-ArWq6zTRx3raG3VA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ArWq6zTRx3raG3VA p{margin:0;}#mermaid-svg-ArWq6zTRx3raG3VA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ArWq6zTRx3raG3VA .cluster-label text{fill:#333;}#mermaid-svg-ArWq6zTRx3raG3VA .cluster-label span{color:#333;}#mermaid-svg-ArWq6zTRx3raG3VA .cluster-label span p{background-color:transparent;}#mermaid-svg-ArWq6zTRx3raG3VA .label text,#mermaid-svg-ArWq6zTRx3raG3VA span{fill:#333;color:#333;}#mermaid-svg-ArWq6zTRx3raG3VA .node rect,#mermaid-svg-ArWq6zTRx3raG3VA .node circle,#mermaid-svg-ArWq6zTRx3raG3VA .node ellipse,#mermaid-svg-ArWq6zTRx3raG3VA .node polygon,#mermaid-svg-ArWq6zTRx3raG3VA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ArWq6zTRx3raG3VA .rough-node .label text,#mermaid-svg-ArWq6zTRx3raG3VA .node .label text,#mermaid-svg-ArWq6zTRx3raG3VA .image-shape .label,#mermaid-svg-ArWq6zTRx3raG3VA .icon-shape .label{text-anchor:middle;}#mermaid-svg-ArWq6zTRx3raG3VA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ArWq6zTRx3raG3VA .rough-node .label,#mermaid-svg-ArWq6zTRx3raG3VA .node .label,#mermaid-svg-ArWq6zTRx3raG3VA .image-shape .label,#mermaid-svg-ArWq6zTRx3raG3VA .icon-shape .label{text-align:center;}#mermaid-svg-ArWq6zTRx3raG3VA .node.clickable{cursor:pointer;}#mermaid-svg-ArWq6zTRx3raG3VA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ArWq6zTRx3raG3VA .arrowheadPath{fill:#333333;}#mermaid-svg-ArWq6zTRx3raG3VA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ArWq6zTRx3raG3VA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ArWq6zTRx3raG3VA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ArWq6zTRx3raG3VA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ArWq6zTRx3raG3VA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ArWq6zTRx3raG3VA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ArWq6zTRx3raG3VA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ArWq6zTRx3raG3VA .cluster text{fill:#333;}#mermaid-svg-ArWq6zTRx3raG3VA .cluster span{color:#333;}#mermaid-svg-ArWq6zTRx3raG3VA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ArWq6zTRx3raG3VA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ArWq6zTRx3raG3VA rect.text{fill:none;stroke-width:0;}#mermaid-svg-ArWq6zTRx3raG3VA .icon-shape,#mermaid-svg-ArWq6zTRx3raG3VA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ArWq6zTRx3raG3VA .icon-shape p,#mermaid-svg-ArWq6zTRx3raG3VA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ArWq6zTRx3raG3VA .icon-shape .label rect,#mermaid-svg-ArWq6zTRx3raG3VA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ArWq6zTRx3raG3VA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ArWq6zTRx3raG3VA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ArWq6zTRx3raG3VA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SuperBuild ------ 第三方依赖层
VTK
ITK / SimpleITK
CTK
Qt
Python
DCMTK
Libs ------ 数据模型与基础库
MRML
Core / Logic / Widgets
DisplayableManager
vtkITK
vtkTeem
vtkSegmentationCore
RemoteIO
Base ------ 应用框架层
QTApp
QTGUI
QTCore
QTCLI
Logic
CLI
Modules ------ 功能模块层
Core
Loadable
Scripted
CLI
Applications ------ 应用层
Applications/SlicerApp
可执行程序、启动入口
命名约定即可判断层次:
| 前缀 | 含义 |
|---|---|
vtk* |
VTK 风格的逻辑 / 数据类 |
qSlicer* |
Qt 风格的 GUI 类 |
vtkMRML* |
场景数据节点 |
qMRML* |
与 MRML 场景双向绑定的 Qt 控件 |
2. SuperBuild ------ 依赖构建层
SuperBuild/ 用 CMake ExternalProject 自动拉取并编译全部第三方库,再构建 Slicer 本体(inner build)。
#mermaid-svg-KHVUCsk60P6ZEb5L{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KHVUCsk60P6ZEb5L .error-icon{fill:#552222;}#mermaid-svg-KHVUCsk60P6ZEb5L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KHVUCsk60P6ZEb5L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KHVUCsk60P6ZEb5L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KHVUCsk60P6ZEb5L .marker.cross{stroke:#333333;}#mermaid-svg-KHVUCsk60P6ZEb5L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KHVUCsk60P6ZEb5L p{margin:0;}#mermaid-svg-KHVUCsk60P6ZEb5L .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KHVUCsk60P6ZEb5L .cluster-label text{fill:#333;}#mermaid-svg-KHVUCsk60P6ZEb5L .cluster-label span{color:#333;}#mermaid-svg-KHVUCsk60P6ZEb5L .cluster-label span p{background-color:transparent;}#mermaid-svg-KHVUCsk60P6ZEb5L .label text,#mermaid-svg-KHVUCsk60P6ZEb5L span{fill:#333;color:#333;}#mermaid-svg-KHVUCsk60P6ZEb5L .node rect,#mermaid-svg-KHVUCsk60P6ZEb5L .node circle,#mermaid-svg-KHVUCsk60P6ZEb5L .node ellipse,#mermaid-svg-KHVUCsk60P6ZEb5L .node polygon,#mermaid-svg-KHVUCsk60P6ZEb5L .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KHVUCsk60P6ZEb5L .rough-node .label text,#mermaid-svg-KHVUCsk60P6ZEb5L .node .label text,#mermaid-svg-KHVUCsk60P6ZEb5L .image-shape .label,#mermaid-svg-KHVUCsk60P6ZEb5L .icon-shape .label{text-anchor:middle;}#mermaid-svg-KHVUCsk60P6ZEb5L .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KHVUCsk60P6ZEb5L .rough-node .label,#mermaid-svg-KHVUCsk60P6ZEb5L .node .label,#mermaid-svg-KHVUCsk60P6ZEb5L .image-shape .label,#mermaid-svg-KHVUCsk60P6ZEb5L .icon-shape .label{text-align:center;}#mermaid-svg-KHVUCsk60P6ZEb5L .node.clickable{cursor:pointer;}#mermaid-svg-KHVUCsk60P6ZEb5L .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KHVUCsk60P6ZEb5L .arrowheadPath{fill:#333333;}#mermaid-svg-KHVUCsk60P6ZEb5L .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KHVUCsk60P6ZEb5L .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KHVUCsk60P6ZEb5L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KHVUCsk60P6ZEb5L .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KHVUCsk60P6ZEb5L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KHVUCsk60P6ZEb5L .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KHVUCsk60P6ZEb5L .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KHVUCsk60P6ZEb5L .cluster text{fill:#333;}#mermaid-svg-KHVUCsk60P6ZEb5L .cluster span{color:#333;}#mermaid-svg-KHVUCsk60P6ZEb5L div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KHVUCsk60P6ZEb5L .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KHVUCsk60P6ZEb5L rect.text{fill:none;stroke-width:0;}#mermaid-svg-KHVUCsk60P6ZEb5L .icon-shape,#mermaid-svg-KHVUCsk60P6ZEb5L .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KHVUCsk60P6ZEb5L .icon-shape p,#mermaid-svg-KHVUCsk60P6ZEb5L .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KHVUCsk60P6ZEb5L .icon-shape .label rect,#mermaid-svg-KHVUCsk60P6ZEb5L .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KHVUCsk60P6ZEb5L .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KHVUCsk60P6ZEb5L .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KHVUCsk60P6ZEb5L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SuperBuild
(outer build)
VTK
ITK
SimpleITK
CTK
CTKAppLauncher
Python
numpy / scipy
PythonQt
DCMTK
SlicerExecutionModel
teem / JsonCpp
LibArchive / OpenSSL
tbb / sqlite / curl
Slicer
(inner build)
| 依赖 | 作用 |
|---|---|
| VTK | 渲染、数据结构、Qt 集成 |
| ITK + SimpleITK | 图像处理与配准算法 |
| CTK | 通用医学影像 Qt 控件(DICOM 浏览器等) |
| Python + numpy/scipy + PythonQt | 脚本层 |
| DCMTK | DICOM 读写 |
| SlicerExecutionModel | CLI 模块的 XML 接口代码生成 |
| teem / JsonCpp / LibArchive / OpenSSL | NRRD、序列化、扩展包、网络 |
| tbb / sqlite / curl / zlib / bzip2 / LZMA | 并行、数据库、网络、压缩 |
顶层 CMakeLists.txt 强制 C++17,并显式拒绝 CMake >= 4.0 以及若干有已知问题的版本(3.21.0、3.25.0~3.25.2)。
3. Libs ------ 数据模型与基础库
3.1 MRML(Medical Reality Markup Language)是整个 Slicer 的核心
Libs/MRML/Core/ 中有 88 个 vtkMRML*Node 类。核心抽象:
-
Libs/MRML/Core/vtkMRMLScene.h单例式的场景容器,持有所有节点,负责节点注册、ID 分配、Import/Export、Undo/Redo,
并通过 VTK 事件机制(
NodeAddedEvent/NodeRemovedEvent/ModifiedEvent)广播变更。整个应用状态就是一棵 MRML 场景树。
-
Libs/MRML/Core/vtkMRMLNode.h所有节点基类,支持
ReadXMLAttributes/WriteXML/Copy,可挂 Attributes 和节点引用(NodeReference)。
3.2 节点"三件套"------Slicer 最重要的设计模式
#mermaid-svg-lPuMAlcynMezWGiV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lPuMAlcynMezWGiV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lPuMAlcynMezWGiV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lPuMAlcynMezWGiV .error-icon{fill:#552222;}#mermaid-svg-lPuMAlcynMezWGiV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lPuMAlcynMezWGiV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lPuMAlcynMezWGiV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lPuMAlcynMezWGiV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lPuMAlcynMezWGiV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lPuMAlcynMezWGiV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lPuMAlcynMezWGiV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lPuMAlcynMezWGiV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lPuMAlcynMezWGiV .marker.cross{stroke:#333333;}#mermaid-svg-lPuMAlcynMezWGiV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lPuMAlcynMezWGiV p{margin:0;}#mermaid-svg-lPuMAlcynMezWGiV g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-lPuMAlcynMezWGiV g.classGroup text .title{font-weight:bolder;}#mermaid-svg-lPuMAlcynMezWGiV .cluster-label text{fill:#333;}#mermaid-svg-lPuMAlcynMezWGiV .cluster-label span{color:#333;}#mermaid-svg-lPuMAlcynMezWGiV .cluster-label span p{background-color:transparent;}#mermaid-svg-lPuMAlcynMezWGiV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lPuMAlcynMezWGiV .cluster text{fill:#333;}#mermaid-svg-lPuMAlcynMezWGiV .cluster span{color:#333;}#mermaid-svg-lPuMAlcynMezWGiV .nodeLabel,#mermaid-svg-lPuMAlcynMezWGiV .edgeLabel{color:#131300;}#mermaid-svg-lPuMAlcynMezWGiV .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-lPuMAlcynMezWGiV .label text{fill:#131300;}#mermaid-svg-lPuMAlcynMezWGiV .labelBkg{background:#ECECFF;}#mermaid-svg-lPuMAlcynMezWGiV .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-lPuMAlcynMezWGiV .classTitle{font-weight:bolder;}#mermaid-svg-lPuMAlcynMezWGiV .node rect,#mermaid-svg-lPuMAlcynMezWGiV .node circle,#mermaid-svg-lPuMAlcynMezWGiV .node ellipse,#mermaid-svg-lPuMAlcynMezWGiV .node polygon,#mermaid-svg-lPuMAlcynMezWGiV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lPuMAlcynMezWGiV .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV g.clickable{cursor:pointer;}#mermaid-svg-lPuMAlcynMezWGiV g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-lPuMAlcynMezWGiV g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-lPuMAlcynMezWGiV .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-lPuMAlcynMezWGiV .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-lPuMAlcynMezWGiV .dashed-line{stroke-dasharray:3;}#mermaid-svg-lPuMAlcynMezWGiV .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-lPuMAlcynMezWGiV #compositionStart,#mermaid-svg-lPuMAlcynMezWGiV .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #compositionEnd,#mermaid-svg-lPuMAlcynMezWGiV .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #dependencyStart,#mermaid-svg-lPuMAlcynMezWGiV .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #dependencyStart,#mermaid-svg-lPuMAlcynMezWGiV .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #extensionStart,#mermaid-svg-lPuMAlcynMezWGiV .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #extensionEnd,#mermaid-svg-lPuMAlcynMezWGiV .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #aggregationStart,#mermaid-svg-lPuMAlcynMezWGiV .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #aggregationEnd,#mermaid-svg-lPuMAlcynMezWGiV .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #lollipopStart,#mermaid-svg-lPuMAlcynMezWGiV .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV #lollipopEnd,#mermaid-svg-lPuMAlcynMezWGiV .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-lPuMAlcynMezWGiV .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-lPuMAlcynMezWGiV .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lPuMAlcynMezWGiV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lPuMAlcynMezWGiV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lPuMAlcynMezWGiV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 持有
引用
引用
引用(非破坏性变换)
1
*
vtkMRMLScene
~NodeAddedEvent~
+AddNode()
+RemoveNode()
+GetNodeByID()
+Import() / Export()
vtkMRMLNode
+ID
+Name
+Attributes
+NodeReferences
+ReadXMLAttributes()
+WriteXML()
+Copy()
vtkMRMLDisplayableNode
存数据本身
图像 / 模型 / 点集
vtkMRMLDisplayNode
存显示属性
颜色 / 透明度 / 可见性
窗宽窗位
vtkMRMLStorageNode
存读写方式
文件路径 / 格式 / 压缩
vtkMRMLTransformNode
刚体 / 仿射 / 形变
可级联成树
| 节点类型 | 职责 | 基类 |
|---|---|---|
| Data 节点 | 存数据本身(图像、模型、点集) | vtkMRMLDisplayableNode |
| Display 节点 | 存显示属性(颜色、透明度、可见性、窗宽窗位) | vtkMRMLDisplayNode |
| Storage 节点 | 存文件读写方式(路径、格式、压缩) | vtkMRMLStorageNode |
3.3 MRML 的其余四个子目录
| 目录 | 职责 |
|---|---|
Libs/MRML/Logic/ |
无 GUI 的业务逻辑(vtkMRMLAbstractLogic 及派生),监听场景事件、操作节点 |
Libs/MRML/DisplayableManager/ |
MRML 节点 → VTK Actor 的桥梁。每种可显示节点有对应的 DisplayableManager,负责在 2D/3D 视图里创建、更新、销毁渲染对象。"数据变了画面自动更新"的实现处 |
Libs/MRML/Widgets/ |
qMRML* 控件:节点选择框、切片视图控件、树视图等,与场景双向绑定 |
Libs/MRML/CLI/ |
命令行模块与 MRML 之间的数据桥接 |
3.4 其他基础库
| 库 | 作用 |
|---|---|
Libs/vtkITK/ |
ITK ↔ VTK 管线适配 |
Libs/vtkTeem/ |
扩散张量 / NRRD |
Libs/vtkSegmentationCore/ |
分割的多表示法(标签图 / 闭合面 / 平面轮廓)自动转换 |
Libs/RemoteIO/ |
远程数据读写 |
Libs/ITKFactoryRegistration/ |
ITK IO 工厂注册 |
4. Base ------ 应用框架层
| 目录 | 内容 |
|---|---|
Base/Logic/ |
vtkSlicerApplicationLogic(应用级逻辑、后台任务队列 vtkSlicerTask)、vtkSlicerModuleLogic(所有模块 Logic 的基类) |
Base/QTCore/ |
无 GUI 的应用内核 :qSlicerCoreApplication、qSlicerCoreIOManager(Reader/Writer 注册表)、模块工厂体系(qSlicerModuleFactoryManager、qSlicerLoadableModuleFactory)、Python 管理、扩展管理器 |
Base/QTGUI/ |
GUI 内核 :qSlicerApplication、qSlicerLayoutManager(视图布局)、模块面板 / 选择器 / 查找对话框、qSlicerAbstractModuleWidget |
Base/QTCLI/ + Base/CLI/ |
CLI 模块的加载、XML 参数解析、自动生成 GUI、进程调度 |
Base/QTApp/ |
主窗口、启动画面、应用生命周期模板 |
Base/Python/ |
slicer.util 等 Python 侧的粘合代码 |
关键设计:Core 与 GUI 分离
#mermaid-svg-YWamyDQavbQ1DIfG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YWamyDQavbQ1DIfG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YWamyDQavbQ1DIfG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YWamyDQavbQ1DIfG .error-icon{fill:#552222;}#mermaid-svg-YWamyDQavbQ1DIfG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YWamyDQavbQ1DIfG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YWamyDQavbQ1DIfG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YWamyDQavbQ1DIfG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YWamyDQavbQ1DIfG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YWamyDQavbQ1DIfG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YWamyDQavbQ1DIfG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YWamyDQavbQ1DIfG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YWamyDQavbQ1DIfG .marker.cross{stroke:#333333;}#mermaid-svg-YWamyDQavbQ1DIfG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YWamyDQavbQ1DIfG p{margin:0;}#mermaid-svg-YWamyDQavbQ1DIfG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YWamyDQavbQ1DIfG .cluster-label text{fill:#333;}#mermaid-svg-YWamyDQavbQ1DIfG .cluster-label span{color:#333;}#mermaid-svg-YWamyDQavbQ1DIfG .cluster-label span p{background-color:transparent;}#mermaid-svg-YWamyDQavbQ1DIfG .label text,#mermaid-svg-YWamyDQavbQ1DIfG span{fill:#333;color:#333;}#mermaid-svg-YWamyDQavbQ1DIfG .node rect,#mermaid-svg-YWamyDQavbQ1DIfG .node circle,#mermaid-svg-YWamyDQavbQ1DIfG .node ellipse,#mermaid-svg-YWamyDQavbQ1DIfG .node polygon,#mermaid-svg-YWamyDQavbQ1DIfG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YWamyDQavbQ1DIfG .rough-node .label text,#mermaid-svg-YWamyDQavbQ1DIfG .node .label text,#mermaid-svg-YWamyDQavbQ1DIfG .image-shape .label,#mermaid-svg-YWamyDQavbQ1DIfG .icon-shape .label{text-anchor:middle;}#mermaid-svg-YWamyDQavbQ1DIfG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YWamyDQavbQ1DIfG .rough-node .label,#mermaid-svg-YWamyDQavbQ1DIfG .node .label,#mermaid-svg-YWamyDQavbQ1DIfG .image-shape .label,#mermaid-svg-YWamyDQavbQ1DIfG .icon-shape .label{text-align:center;}#mermaid-svg-YWamyDQavbQ1DIfG .node.clickable{cursor:pointer;}#mermaid-svg-YWamyDQavbQ1DIfG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YWamyDQavbQ1DIfG .arrowheadPath{fill:#333333;}#mermaid-svg-YWamyDQavbQ1DIfG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YWamyDQavbQ1DIfG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YWamyDQavbQ1DIfG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YWamyDQavbQ1DIfG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YWamyDQavbQ1DIfG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YWamyDQavbQ1DIfG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YWamyDQavbQ1DIfG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YWamyDQavbQ1DIfG .cluster text{fill:#333;}#mermaid-svg-YWamyDQavbQ1DIfG .cluster span{color:#333;}#mermaid-svg-YWamyDQavbQ1DIfG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YWamyDQavbQ1DIfG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YWamyDQavbQ1DIfG rect.text{fill:none;stroke-width:0;}#mermaid-svg-YWamyDQavbQ1DIfG .icon-shape,#mermaid-svg-YWamyDQavbQ1DIfG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YWamyDQavbQ1DIfG .icon-shape p,#mermaid-svg-YWamyDQavbQ1DIfG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YWamyDQavbQ1DIfG .icon-shape .label rect,#mermaid-svg-YWamyDQavbQ1DIfG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YWamyDQavbQ1DIfG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YWamyDQavbQ1DIfG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YWamyDQavbQ1DIfG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 继承
qSlicerCoreApplication
(Base/QTCore)
qSlicerApplication
(Base/QTGUI)
无界面模式
--no-main-window
批处理 / 自动化测试
完整 GUI 模式
主窗口 / 布局管理 / 模块面板
这一分离使得 Slicer 可以在 无界面模式 下运行完整的数据与逻辑栈。
5. Modules ------ 四类模块机制
这是 Slicer 可扩展性的核心。四种类型,能力和开发成本递增:
#mermaid-svg-GEvcZ6wcEKxT8oVK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GEvcZ6wcEKxT8oVK .error-icon{fill:#552222;}#mermaid-svg-GEvcZ6wcEKxT8oVK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GEvcZ6wcEKxT8oVK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .marker.cross{stroke:#333333;}#mermaid-svg-GEvcZ6wcEKxT8oVK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GEvcZ6wcEKxT8oVK p{margin:0;}#mermaid-svg-GEvcZ6wcEKxT8oVK .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .cluster-label text{fill:#333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .cluster-label span{color:#333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .cluster-label span p{background-color:transparent;}#mermaid-svg-GEvcZ6wcEKxT8oVK .label text,#mermaid-svg-GEvcZ6wcEKxT8oVK span{fill:#333;color:#333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .node rect,#mermaid-svg-GEvcZ6wcEKxT8oVK .node circle,#mermaid-svg-GEvcZ6wcEKxT8oVK .node ellipse,#mermaid-svg-GEvcZ6wcEKxT8oVK .node polygon,#mermaid-svg-GEvcZ6wcEKxT8oVK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GEvcZ6wcEKxT8oVK .rough-node .label text,#mermaid-svg-GEvcZ6wcEKxT8oVK .node .label text,#mermaid-svg-GEvcZ6wcEKxT8oVK .image-shape .label,#mermaid-svg-GEvcZ6wcEKxT8oVK .icon-shape .label{text-anchor:middle;}#mermaid-svg-GEvcZ6wcEKxT8oVK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GEvcZ6wcEKxT8oVK .rough-node .label,#mermaid-svg-GEvcZ6wcEKxT8oVK .node .label,#mermaid-svg-GEvcZ6wcEKxT8oVK .image-shape .label,#mermaid-svg-GEvcZ6wcEKxT8oVK .icon-shape .label{text-align:center;}#mermaid-svg-GEvcZ6wcEKxT8oVK .node.clickable{cursor:pointer;}#mermaid-svg-GEvcZ6wcEKxT8oVK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .arrowheadPath{fill:#333333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GEvcZ6wcEKxT8oVK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GEvcZ6wcEKxT8oVK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GEvcZ6wcEKxT8oVK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GEvcZ6wcEKxT8oVK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GEvcZ6wcEKxT8oVK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GEvcZ6wcEKxT8oVK .cluster text{fill:#333;}#mermaid-svg-GEvcZ6wcEKxT8oVK .cluster span{color:#333;}#mermaid-svg-GEvcZ6wcEKxT8oVK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GEvcZ6wcEKxT8oVK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GEvcZ6wcEKxT8oVK rect.text{fill:none;stroke-width:0;}#mermaid-svg-GEvcZ6wcEKxT8oVK .icon-shape,#mermaid-svg-GEvcZ6wcEKxT8oVK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GEvcZ6wcEKxT8oVK .icon-shape p,#mermaid-svg-GEvcZ6wcEKxT8oVK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GEvcZ6wcEKxT8oVK .icon-shape .label rect,#mermaid-svg-GEvcZ6wcEKxT8oVK .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GEvcZ6wcEKxT8oVK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GEvcZ6wcEKxT8oVK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GEvcZ6wcEKxT8oVK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 能力递增
CLI 模块
XML 描述参数
GUI 自动生成
Scripted 模块
Python
可自定义界面
Loadable 模块
C++ 共享库
可注册 MRML 节点
可注册 DisplayableManager
Core 模块
内建、不可卸载
| 类型 | 语言 | 位置 | 特点 |
|---|---|---|---|
| CLI | C++ / 任意可执行文件 | Modules/CLI/ |
用 XML 描述参数,SlicerExecutionModel 自动生成 GUI 和命令行解析;独立进程或共享库运行;最易写,但无自定义界面 |
| Loadable | C++ | Modules/Loadable/ |
编译成共享库,运行时被 qSlicerLoadableModuleFactory 发现加载;可自定义 Qt 界面、注册新 MRML 节点类型、注册 DisplayableManager。功能最完整。共 22 个内置模块 |
| Scripted | Python | Modules/Scripted/ |
通过 qSlicerScriptedLoadableModule 加载,能力接近 Loadable,开发迭代最快 |
| Core | C++ | Modules/Core/ |
内建进主程序,不可卸载,如 EventBroker |
示例:
- CLI :
ThresholdScalarVolume、N4ITKBiasFieldCorrection、ResampleDTIVolume - Loadable :
Volumes、Models、Markups、Segmentations、Transforms、Sequences、VolumeRendering、SubjectHierarchy、Terminologies - Scripted :
DICOM、SegmentEditor、SampleData、ScreenCapture、ExtensionWizard、WebServer
5.1 每个 Loadable 模块的标准三件套(MVC)
以 Modules/Loadable/Volumes/ 为例:
#mermaid-svg-Zecbd6rBuoE6Xsen{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Zecbd6rBuoE6Xsen .error-icon{fill:#552222;}#mermaid-svg-Zecbd6rBuoE6Xsen .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Zecbd6rBuoE6Xsen .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Zecbd6rBuoE6Xsen .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Zecbd6rBuoE6Xsen .marker.cross{stroke:#333333;}#mermaid-svg-Zecbd6rBuoE6Xsen svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Zecbd6rBuoE6Xsen p{margin:0;}#mermaid-svg-Zecbd6rBuoE6Xsen .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Zecbd6rBuoE6Xsen .cluster-label text{fill:#333;}#mermaid-svg-Zecbd6rBuoE6Xsen .cluster-label span{color:#333;}#mermaid-svg-Zecbd6rBuoE6Xsen .cluster-label span p{background-color:transparent;}#mermaid-svg-Zecbd6rBuoE6Xsen .label text,#mermaid-svg-Zecbd6rBuoE6Xsen span{fill:#333;color:#333;}#mermaid-svg-Zecbd6rBuoE6Xsen .node rect,#mermaid-svg-Zecbd6rBuoE6Xsen .node circle,#mermaid-svg-Zecbd6rBuoE6Xsen .node ellipse,#mermaid-svg-Zecbd6rBuoE6Xsen .node polygon,#mermaid-svg-Zecbd6rBuoE6Xsen .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Zecbd6rBuoE6Xsen .rough-node .label text,#mermaid-svg-Zecbd6rBuoE6Xsen .node .label text,#mermaid-svg-Zecbd6rBuoE6Xsen .image-shape .label,#mermaid-svg-Zecbd6rBuoE6Xsen .icon-shape .label{text-anchor:middle;}#mermaid-svg-Zecbd6rBuoE6Xsen .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Zecbd6rBuoE6Xsen .rough-node .label,#mermaid-svg-Zecbd6rBuoE6Xsen .node .label,#mermaid-svg-Zecbd6rBuoE6Xsen .image-shape .label,#mermaid-svg-Zecbd6rBuoE6Xsen .icon-shape .label{text-align:center;}#mermaid-svg-Zecbd6rBuoE6Xsen .node.clickable{cursor:pointer;}#mermaid-svg-Zecbd6rBuoE6Xsen .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Zecbd6rBuoE6Xsen .arrowheadPath{fill:#333333;}#mermaid-svg-Zecbd6rBuoE6Xsen .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Zecbd6rBuoE6Xsen .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Zecbd6rBuoE6Xsen .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Zecbd6rBuoE6Xsen .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Zecbd6rBuoE6Xsen .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Zecbd6rBuoE6Xsen .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Zecbd6rBuoE6Xsen .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Zecbd6rBuoE6Xsen .cluster text{fill:#333;}#mermaid-svg-Zecbd6rBuoE6Xsen .cluster span{color:#333;}#mermaid-svg-Zecbd6rBuoE6Xsen div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Zecbd6rBuoE6Xsen .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Zecbd6rBuoE6Xsen rect.text{fill:none;stroke-width:0;}#mermaid-svg-Zecbd6rBuoE6Xsen .icon-shape,#mermaid-svg-Zecbd6rBuoE6Xsen .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Zecbd6rBuoE6Xsen .icon-shape p,#mermaid-svg-Zecbd6rBuoE6Xsen .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Zecbd6rBuoE6Xsen .icon-shape .label rect,#mermaid-svg-Zecbd6rBuoE6Xsen .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Zecbd6rBuoE6Xsen .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Zecbd6rBuoE6Xsen .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Zecbd6rBuoE6Xsen :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Model
Controller
View
Modules/Loadable/Volumes/
MRML/
模块新增的 MRML 节点类型
Logic/
vtkSlicerVolumesModuleLogic
纯算法与场景操作,不依赖 Qt
Widgets/
qMRML* 自定义控件
qSlicerVolumesModule.cxx
模块注册入口
qSlicerVolumesModuleWidget.cxx
Qt 界面
这个分层保证了 Logic 可被 Python 和无头模式直接调用,界面只是薄壳。
6. Applications 与扩展
Applications/SlicerApp/------ 主程序入口。通过 CMake 的 "Custom App" 机制,
第三方可复用整套框架打造自有品牌应用。Extensions/------ 扩展打包 / 发布的 CMake 支撑;实际扩展代码托管在 Extensions Index,
由qSlicerExtensionsManagerModel在运行时下载安装,无需重编译主程序。
详见本文第二部分。
7. 运行时数据流
把上述各层串起来,以"加载一个体数据"为例:
模块 Logic qMRML 控件 DisplayableManager vtkMRMLScene vtkMRMLStorageNode qSlicerFileReader qSlicerCoreIOManager 模块 Logic qMRML 控件 DisplayableManager vtkMRMLScene vtkMRMLStorageNode qSlicerFileReader qSlicerCoreIOManager #mermaid-svg-0ddMCFW0WXq50rvv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0ddMCFW0WXq50rvv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0ddMCFW0WXq50rvv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0ddMCFW0WXq50rvv .error-icon{fill:#552222;}#mermaid-svg-0ddMCFW0WXq50rvv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0ddMCFW0WXq50rvv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0ddMCFW0WXq50rvv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0ddMCFW0WXq50rvv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0ddMCFW0WXq50rvv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0ddMCFW0WXq50rvv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0ddMCFW0WXq50rvv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0ddMCFW0WXq50rvv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0ddMCFW0WXq50rvv .marker.cross{stroke:#333333;}#mermaid-svg-0ddMCFW0WXq50rvv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0ddMCFW0WXq50rvv p{margin:0;}#mermaid-svg-0ddMCFW0WXq50rvv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0ddMCFW0WXq50rvv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-0ddMCFW0WXq50rvv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0ddMCFW0WXq50rvv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-0ddMCFW0WXq50rvv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-0ddMCFW0WXq50rvv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-0ddMCFW0WXq50rvv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-0ddMCFW0WXq50rvv .sequenceNumber{fill:white;}#mermaid-svg-0ddMCFW0WXq50rvv #sequencenumber{fill:#333;}#mermaid-svg-0ddMCFW0WXq50rvv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-0ddMCFW0WXq50rvv .messageText{fill:#333;stroke:none;}#mermaid-svg-0ddMCFW0WXq50rvv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0ddMCFW0WXq50rvv .labelText,#mermaid-svg-0ddMCFW0WXq50rvv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-0ddMCFW0WXq50rvv .loopText,#mermaid-svg-0ddMCFW0WXq50rvv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-0ddMCFW0WXq50rvv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0ddMCFW0WXq50rvv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-0ddMCFW0WXq50rvv .noteText,#mermaid-svg-0ddMCFW0WXq50rvv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-0ddMCFW0WXq50rvv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0ddMCFW0WXq50rvv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0ddMCFW0WXq50rvv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0ddMCFW0WXq50rvv .actorPopupMenu{position:absolute;}#mermaid-svg-0ddMCFW0WXq50rvv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-0ddMCFW0WXq50rvv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0ddMCFW0WXq50rvv .actor-man circle,#mermaid-svg-0ddMCFW0WXq50rvv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-0ddMCFW0WXq50rvv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户 点击 "Load Volume" 1 按扩展名匹配 Reader 2 创建 VolumeNode + DisplayNode + StorageNode 3 ReadData() 4 调用 ITK / vtkITK 读盘 5 AddNode() × 3 6 NodeAddedEvent 7 NodeAddedEvent 8 NodeAddedEvent 9 创建 VTK Actor → 2D/3D 视图出图 10 节点下拉框自动刷新 11 更新自身状态 12 拖动窗宽窗位滑块 13 仅修改 DisplayNode 14 ModifiedEvent 15 更新 Actor 属性(不重新读数据) 16 用户
关键点:改显示属性只碰 DisplayNode,数据节点完全不动------这就是三节点分离的实际收益。
8. 第一部分小结
Slicer 是"以 MRML 场景为唯一真相源、以事件驱动同步视图、以四类可插拔模块扩展功能"的分层架构。
贯穿始终的两条主线:
- Data / Display / Storage 三节点分离
- Logic / GUI 分离
第二部分:扩展机制底层基础
9. 核心结论
扩展(Extension)不是一套独立的插件框架,它复用的就是模块(Module)的加载机制。
扩展 = 一组按约定目录结构打包的模块 + 一份元数据 + 一套分发 / 版本绑定设施。
Slicer 没有为扩展发明任何新的插件加载技术,它只是把"模块搜索路径"这个变量暴露出来,
剩下的全是打包、版本绑定和分发的工程问题。
#mermaid-svg-2PJTRVIjg5r9M7Sk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2PJTRVIjg5r9M7Sk .error-icon{fill:#552222;}#mermaid-svg-2PJTRVIjg5r9M7Sk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2PJTRVIjg5r9M7Sk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .marker.cross{stroke:#333333;}#mermaid-svg-2PJTRVIjg5r9M7Sk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2PJTRVIjg5r9M7Sk p{margin:0;}#mermaid-svg-2PJTRVIjg5r9M7Sk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .cluster-label text{fill:#333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .cluster-label span{color:#333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .cluster-label span p{background-color:transparent;}#mermaid-svg-2PJTRVIjg5r9M7Sk .label text,#mermaid-svg-2PJTRVIjg5r9M7Sk span{fill:#333;color:#333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .node rect,#mermaid-svg-2PJTRVIjg5r9M7Sk .node circle,#mermaid-svg-2PJTRVIjg5r9M7Sk .node ellipse,#mermaid-svg-2PJTRVIjg5r9M7Sk .node polygon,#mermaid-svg-2PJTRVIjg5r9M7Sk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2PJTRVIjg5r9M7Sk .rough-node .label text,#mermaid-svg-2PJTRVIjg5r9M7Sk .node .label text,#mermaid-svg-2PJTRVIjg5r9M7Sk .image-shape .label,#mermaid-svg-2PJTRVIjg5r9M7Sk .icon-shape .label{text-anchor:middle;}#mermaid-svg-2PJTRVIjg5r9M7Sk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2PJTRVIjg5r9M7Sk .rough-node .label,#mermaid-svg-2PJTRVIjg5r9M7Sk .node .label,#mermaid-svg-2PJTRVIjg5r9M7Sk .image-shape .label,#mermaid-svg-2PJTRVIjg5r9M7Sk .icon-shape .label{text-align:center;}#mermaid-svg-2PJTRVIjg5r9M7Sk .node.clickable{cursor:pointer;}#mermaid-svg-2PJTRVIjg5r9M7Sk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .arrowheadPath{fill:#333333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2PJTRVIjg5r9M7Sk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2PJTRVIjg5r9M7Sk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2PJTRVIjg5r9M7Sk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2PJTRVIjg5r9M7Sk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2PJTRVIjg5r9M7Sk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2PJTRVIjg5r9M7Sk .cluster text{fill:#333;}#mermaid-svg-2PJTRVIjg5r9M7Sk .cluster span{color:#333;}#mermaid-svg-2PJTRVIjg5r9M7Sk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2PJTRVIjg5r9M7Sk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2PJTRVIjg5r9M7Sk rect.text{fill:none;stroke-width:0;}#mermaid-svg-2PJTRVIjg5r9M7Sk .icon-shape,#mermaid-svg-2PJTRVIjg5r9M7Sk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2PJTRVIjg5r9M7Sk .icon-shape p,#mermaid-svg-2PJTRVIjg5r9M7Sk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2PJTRVIjg5r9M7Sk .icon-shape .label rect,#mermaid-svg-2PJTRVIjg5r9M7Sk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2PJTRVIjg5r9M7Sk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2PJTRVIjg5r9M7Sk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2PJTRVIjg5r9M7Sk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 扩展 Extension
支柱一:模块搜索路径
Modules/AdditionalPaths
支柱二:CTK AppLauncher
运行时环境注入
支柱三:版本 / ABI 绑定
revision × OS × arch
支柱四:CMake 构建体系
SlicerConfig.cmake
支柱五:元数据与分发
.s4ext / JSON catalog
10. 支柱一:模块搜索路径(真正的"加载"机制)
这是最关键、也最简单的一环。见
Base/QTCore/qSlicerExtensionsManagerModel.cxx:1167 的 extensionModulePaths():
cpp
QString path = this->extensionInstallPath(extensionName); // <ExtInstallPath>/<ExtName>
return appendToPathList(QStringList(), QStringList()
<< path + "/" Slicer_CLIMODULES_SUBDIR // cli-modules
<< path + "/" + Slicer_CLIMODULES_LIB_DIR // lib/Slicer-5.8/cli-modules
<< path + "/" + Slicer_QTLOADABLEMODULES_LIB_DIR // lib/Slicer-5.8/qt-loadable-modules
#ifdef Slicer_USE_PYTHONQT
<< path + "/" + Slicer_QTSCRIPTEDMODULES_LIB_DIR // lib/Slicer-5.8/qt-scripted-modules
#endif
);
10.1 扩展包的目录结构必须和 Slicer 安装树同构
#mermaid-svg-FubbpSHXLhTcuQfv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FubbpSHXLhTcuQfv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FubbpSHXLhTcuQfv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FubbpSHXLhTcuQfv .error-icon{fill:#552222;}#mermaid-svg-FubbpSHXLhTcuQfv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FubbpSHXLhTcuQfv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FubbpSHXLhTcuQfv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FubbpSHXLhTcuQfv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FubbpSHXLhTcuQfv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FubbpSHXLhTcuQfv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FubbpSHXLhTcuQfv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FubbpSHXLhTcuQfv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FubbpSHXLhTcuQfv .marker.cross{stroke:#333333;}#mermaid-svg-FubbpSHXLhTcuQfv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FubbpSHXLhTcuQfv p{margin:0;}#mermaid-svg-FubbpSHXLhTcuQfv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FubbpSHXLhTcuQfv .cluster-label text{fill:#333;}#mermaid-svg-FubbpSHXLhTcuQfv .cluster-label span{color:#333;}#mermaid-svg-FubbpSHXLhTcuQfv .cluster-label span p{background-color:transparent;}#mermaid-svg-FubbpSHXLhTcuQfv .label text,#mermaid-svg-FubbpSHXLhTcuQfv span{fill:#333;color:#333;}#mermaid-svg-FubbpSHXLhTcuQfv .node rect,#mermaid-svg-FubbpSHXLhTcuQfv .node circle,#mermaid-svg-FubbpSHXLhTcuQfv .node ellipse,#mermaid-svg-FubbpSHXLhTcuQfv .node polygon,#mermaid-svg-FubbpSHXLhTcuQfv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FubbpSHXLhTcuQfv .rough-node .label text,#mermaid-svg-FubbpSHXLhTcuQfv .node .label text,#mermaid-svg-FubbpSHXLhTcuQfv .image-shape .label,#mermaid-svg-FubbpSHXLhTcuQfv .icon-shape .label{text-anchor:middle;}#mermaid-svg-FubbpSHXLhTcuQfv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FubbpSHXLhTcuQfv .rough-node .label,#mermaid-svg-FubbpSHXLhTcuQfv .node .label,#mermaid-svg-FubbpSHXLhTcuQfv .image-shape .label,#mermaid-svg-FubbpSHXLhTcuQfv .icon-shape .label{text-align:center;}#mermaid-svg-FubbpSHXLhTcuQfv .node.clickable{cursor:pointer;}#mermaid-svg-FubbpSHXLhTcuQfv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FubbpSHXLhTcuQfv .arrowheadPath{fill:#333333;}#mermaid-svg-FubbpSHXLhTcuQfv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FubbpSHXLhTcuQfv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FubbpSHXLhTcuQfv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FubbpSHXLhTcuQfv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FubbpSHXLhTcuQfv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FubbpSHXLhTcuQfv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FubbpSHXLhTcuQfv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FubbpSHXLhTcuQfv .cluster text{fill:#333;}#mermaid-svg-FubbpSHXLhTcuQfv .cluster span{color:#333;}#mermaid-svg-FubbpSHXLhTcuQfv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FubbpSHXLhTcuQfv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FubbpSHXLhTcuQfv rect.text{fill:none;stroke-width:0;}#mermaid-svg-FubbpSHXLhTcuQfv .icon-shape,#mermaid-svg-FubbpSHXLhTcuQfv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FubbpSHXLhTcuQfv .icon-shape p,#mermaid-svg-FubbpSHXLhTcuQfv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FubbpSHXLhTcuQfv .icon-shape .label rect,#mermaid-svg-FubbpSHXLhTcuQfv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FubbpSHXLhTcuQfv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FubbpSHXLhTcuQfv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FubbpSHXLhTcuQfv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 结构同构
Slicer 安装树
lib/Slicer-5.8/cli-modules/
lib/Slicer-5.8/qt-loadable-modules/
lib/Slicer-5.8/qt-scripted-modules/
扩展安装目录 <ExtInstallPath>/MyExtension/
cli-modules/
lib/Slicer-5.8/cli-modules/
lib/Slicer-5.8/qt-loadable-modules/
lib/Slicer-5.8/qt-scripted-modules/
10.2 安装 = 往 QSettings 追加路径
qSlicerExtensionsManagerModel.cxx:690-692:
cpp
QStringList additionalPaths = app->toSlicerHomeAbsolutePaths(
settings.value("Modules/AdditionalPaths").toStringList());
QStringList newModulePaths = q->extensionModulePaths(extensionName);
settings.setValue("Modules/AdditionalPaths", /* 合并后转相对路径存回 */);
下次启动时,模块工厂读这个键:
| 文件 | 负责 |
|---|---|
Base/QTCore/qSlicerLoadableModuleFactory.cxx:122 |
C++ 共享库模块 |
Base/QTGUI/qSlicerScriptedLoadableModuleFactory.cxx:163 |
Python 模块 |
cpp
QStringList additionalModulePaths = app->toSlicerHomeAbsolutePaths(
settings->value("Modules/AdditionalPaths").toStringList());
QStringList qtModulePaths = additionalModulePaths + defaultQTModulePaths;
注意顺序 :
additionalModulePaths排在内置路径之前 ,因此扩展可以覆盖同名内置模块。
10.3 推论:为什么装完扩展必须重启
#mermaid-svg-ofmJCd6rkqg0alay{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ofmJCd6rkqg0alay .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ofmJCd6rkqg0alay .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ofmJCd6rkqg0alay .error-icon{fill:#552222;}#mermaid-svg-ofmJCd6rkqg0alay .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ofmJCd6rkqg0alay .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ofmJCd6rkqg0alay .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ofmJCd6rkqg0alay .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ofmJCd6rkqg0alay .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ofmJCd6rkqg0alay .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ofmJCd6rkqg0alay .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ofmJCd6rkqg0alay .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ofmJCd6rkqg0alay .marker.cross{stroke:#333333;}#mermaid-svg-ofmJCd6rkqg0alay svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ofmJCd6rkqg0alay p{margin:0;}#mermaid-svg-ofmJCd6rkqg0alay .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ofmJCd6rkqg0alay .cluster-label text{fill:#333;}#mermaid-svg-ofmJCd6rkqg0alay .cluster-label span{color:#333;}#mermaid-svg-ofmJCd6rkqg0alay .cluster-label span p{background-color:transparent;}#mermaid-svg-ofmJCd6rkqg0alay .label text,#mermaid-svg-ofmJCd6rkqg0alay span{fill:#333;color:#333;}#mermaid-svg-ofmJCd6rkqg0alay .node rect,#mermaid-svg-ofmJCd6rkqg0alay .node circle,#mermaid-svg-ofmJCd6rkqg0alay .node ellipse,#mermaid-svg-ofmJCd6rkqg0alay .node polygon,#mermaid-svg-ofmJCd6rkqg0alay .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ofmJCd6rkqg0alay .rough-node .label text,#mermaid-svg-ofmJCd6rkqg0alay .node .label text,#mermaid-svg-ofmJCd6rkqg0alay .image-shape .label,#mermaid-svg-ofmJCd6rkqg0alay .icon-shape .label{text-anchor:middle;}#mermaid-svg-ofmJCd6rkqg0alay .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ofmJCd6rkqg0alay .rough-node .label,#mermaid-svg-ofmJCd6rkqg0alay .node .label,#mermaid-svg-ofmJCd6rkqg0alay .image-shape .label,#mermaid-svg-ofmJCd6rkqg0alay .icon-shape .label{text-align:center;}#mermaid-svg-ofmJCd6rkqg0alay .node.clickable{cursor:pointer;}#mermaid-svg-ofmJCd6rkqg0alay .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ofmJCd6rkqg0alay .arrowheadPath{fill:#333333;}#mermaid-svg-ofmJCd6rkqg0alay .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ofmJCd6rkqg0alay .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ofmJCd6rkqg0alay .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ofmJCd6rkqg0alay .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ofmJCd6rkqg0alay .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ofmJCd6rkqg0alay .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ofmJCd6rkqg0alay .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ofmJCd6rkqg0alay .cluster text{fill:#333;}#mermaid-svg-ofmJCd6rkqg0alay .cluster span{color:#333;}#mermaid-svg-ofmJCd6rkqg0alay div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ofmJCd6rkqg0alay .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ofmJCd6rkqg0alay rect.text{fill:none;stroke-width:0;}#mermaid-svg-ofmJCd6rkqg0alay .icon-shape,#mermaid-svg-ofmJCd6rkqg0alay .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ofmJCd6rkqg0alay .icon-shape p,#mermaid-svg-ofmJCd6rkqg0alay .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ofmJCd6rkqg0alay .icon-shape .label rect,#mermaid-svg-ofmJCd6rkqg0alay .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ofmJCd6rkqg0alay .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ofmJCd6rkqg0alay .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ofmJCd6rkqg0alay :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用启动
模块工厂扫描路径
(一次性)
dlopen 加载共享库
运行中
用户安装新扩展
写入 Modules/AdditionalPaths
但本次会话不再扫描
标记为 Installed 但未 Loaded
提示重启
用户卸载 / 更新扩展
scheduleForUninstall / Update
仅记标记,不动文件
(共享库已被 dlopen 锁住)
真正的文件操作由 uninstallScheduledExtensions() / updateScheduledExtensions()
在下次启动的早期阶段执行。
10.4 开发者绕过重启的入口
命令行 --additional-module-paths(Base/QTGUI/qSlicerApplication.cxx:1344-1367),
它甚至能直接接受一个扩展目录,内部自动展开成上面那 4 个子路径。
11. 支柱二:CTK AppLauncher ------ 运行时环境注入
模块路径解决了"找得到",但 C++ 扩展还依赖第三方 .so / .dll,
Python 扩展还需要 PYTHONPATH。
Slicer 的可执行文件其实是 CTKAppLauncher 的一层壳:
#mermaid-svg-kIE3CKpP0U8OoIgO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kIE3CKpP0U8OoIgO .error-icon{fill:#552222;}#mermaid-svg-kIE3CKpP0U8OoIgO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kIE3CKpP0U8OoIgO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kIE3CKpP0U8OoIgO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kIE3CKpP0U8OoIgO .marker.cross{stroke:#333333;}#mermaid-svg-kIE3CKpP0U8OoIgO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kIE3CKpP0U8OoIgO p{margin:0;}#mermaid-svg-kIE3CKpP0U8OoIgO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kIE3CKpP0U8OoIgO .cluster-label text{fill:#333;}#mermaid-svg-kIE3CKpP0U8OoIgO .cluster-label span{color:#333;}#mermaid-svg-kIE3CKpP0U8OoIgO .cluster-label span p{background-color:transparent;}#mermaid-svg-kIE3CKpP0U8OoIgO .label text,#mermaid-svg-kIE3CKpP0U8OoIgO span{fill:#333;color:#333;}#mermaid-svg-kIE3CKpP0U8OoIgO .node rect,#mermaid-svg-kIE3CKpP0U8OoIgO .node circle,#mermaid-svg-kIE3CKpP0U8OoIgO .node ellipse,#mermaid-svg-kIE3CKpP0U8OoIgO .node polygon,#mermaid-svg-kIE3CKpP0U8OoIgO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kIE3CKpP0U8OoIgO .rough-node .label text,#mermaid-svg-kIE3CKpP0U8OoIgO .node .label text,#mermaid-svg-kIE3CKpP0U8OoIgO .image-shape .label,#mermaid-svg-kIE3CKpP0U8OoIgO .icon-shape .label{text-anchor:middle;}#mermaid-svg-kIE3CKpP0U8OoIgO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kIE3CKpP0U8OoIgO .rough-node .label,#mermaid-svg-kIE3CKpP0U8OoIgO .node .label,#mermaid-svg-kIE3CKpP0U8OoIgO .image-shape .label,#mermaid-svg-kIE3CKpP0U8OoIgO .icon-shape .label{text-align:center;}#mermaid-svg-kIE3CKpP0U8OoIgO .node.clickable{cursor:pointer;}#mermaid-svg-kIE3CKpP0U8OoIgO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kIE3CKpP0U8OoIgO .arrowheadPath{fill:#333333;}#mermaid-svg-kIE3CKpP0U8OoIgO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kIE3CKpP0U8OoIgO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kIE3CKpP0U8OoIgO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kIE3CKpP0U8OoIgO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kIE3CKpP0U8OoIgO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kIE3CKpP0U8OoIgO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kIE3CKpP0U8OoIgO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kIE3CKpP0U8OoIgO .cluster text{fill:#333;}#mermaid-svg-kIE3CKpP0U8OoIgO .cluster span{color:#333;}#mermaid-svg-kIE3CKpP0U8OoIgO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kIE3CKpP0U8OoIgO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kIE3CKpP0U8OoIgO rect.text{fill:none;stroke-width:0;}#mermaid-svg-kIE3CKpP0U8OoIgO .icon-shape,#mermaid-svg-kIE3CKpP0U8OoIgO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kIE3CKpP0U8OoIgO .icon-shape p,#mermaid-svg-kIE3CKpP0U8OoIgO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kIE3CKpP0U8OoIgO .icon-shape .label rect,#mermaid-svg-kIE3CKpP0U8OoIgO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kIE3CKpP0U8OoIgO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kIE3CKpP0U8OoIgO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kIE3CKpP0U8OoIgO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户双击 Slicer
CTKAppLauncher
(Slicer.exe)
读取 .ini 配置
拼装环境变量
PATH / LD_LIBRARY_PATH
DYLD_LIBRARY_PATH
PYTHONPATH / QT_PLUGIN_PATH
exec SlicerApp-real
AdditionalLauncherSettings.ini
(扩展生成)
构建树上,扩展通过 Extensions/CMake/SlicerBlockAdditionalLauncherSettings.cmake
生成 AdditionalLauncherSettings.ini,把自己的 lib/、bin/、
超级构建里的第三方库目录,以及所有依赖扩展的对应路径都追加进去:
cmake
list(INSERT Slicer_LAUNCH_COMMAND ${launch_index}
"--launcher-additional-settings" ${Slicer_ADDITIONAL_LAUNCHER_SETTINGS_FILE})
...
foreach(dep ${EXTENSION_DEPENDS})
list(APPEND EXTENSION_LIBRARY_PATHS_BUILD ${${dep}_LIBRARY_PATHS_LAUNCHER_BUILD})
endforeach()
这就是为什么你能在扩展的构建树里
make完直接跑起来调试,不需要先安装。
12. 支柱三:版本与 ABI 绑定
C++ 扩展和主程序共享 VTK / ITK / Qt 的 ABI,二进制不兼容会直接崩溃。
Slicer 用三元组做硬绑定:
#mermaid-svg-8WNPwQlx3rIl8lit{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8WNPwQlx3rIl8lit .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8WNPwQlx3rIl8lit .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8WNPwQlx3rIl8lit .error-icon{fill:#552222;}#mermaid-svg-8WNPwQlx3rIl8lit .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8WNPwQlx3rIl8lit .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8WNPwQlx3rIl8lit .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8WNPwQlx3rIl8lit .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8WNPwQlx3rIl8lit .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8WNPwQlx3rIl8lit .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8WNPwQlx3rIl8lit .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8WNPwQlx3rIl8lit .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8WNPwQlx3rIl8lit .marker.cross{stroke:#333333;}#mermaid-svg-8WNPwQlx3rIl8lit svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8WNPwQlx3rIl8lit p{margin:0;}#mermaid-svg-8WNPwQlx3rIl8lit .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8WNPwQlx3rIl8lit .cluster-label text{fill:#333;}#mermaid-svg-8WNPwQlx3rIl8lit .cluster-label span{color:#333;}#mermaid-svg-8WNPwQlx3rIl8lit .cluster-label span p{background-color:transparent;}#mermaid-svg-8WNPwQlx3rIl8lit .label text,#mermaid-svg-8WNPwQlx3rIl8lit span{fill:#333;color:#333;}#mermaid-svg-8WNPwQlx3rIl8lit .node rect,#mermaid-svg-8WNPwQlx3rIl8lit .node circle,#mermaid-svg-8WNPwQlx3rIl8lit .node ellipse,#mermaid-svg-8WNPwQlx3rIl8lit .node polygon,#mermaid-svg-8WNPwQlx3rIl8lit .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8WNPwQlx3rIl8lit .rough-node .label text,#mermaid-svg-8WNPwQlx3rIl8lit .node .label text,#mermaid-svg-8WNPwQlx3rIl8lit .image-shape .label,#mermaid-svg-8WNPwQlx3rIl8lit .icon-shape .label{text-anchor:middle;}#mermaid-svg-8WNPwQlx3rIl8lit .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8WNPwQlx3rIl8lit .rough-node .label,#mermaid-svg-8WNPwQlx3rIl8lit .node .label,#mermaid-svg-8WNPwQlx3rIl8lit .image-shape .label,#mermaid-svg-8WNPwQlx3rIl8lit .icon-shape .label{text-align:center;}#mermaid-svg-8WNPwQlx3rIl8lit .node.clickable{cursor:pointer;}#mermaid-svg-8WNPwQlx3rIl8lit .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8WNPwQlx3rIl8lit .arrowheadPath{fill:#333333;}#mermaid-svg-8WNPwQlx3rIl8lit .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8WNPwQlx3rIl8lit .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8WNPwQlx3rIl8lit .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8WNPwQlx3rIl8lit .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8WNPwQlx3rIl8lit .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8WNPwQlx3rIl8lit .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8WNPwQlx3rIl8lit .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8WNPwQlx3rIl8lit .cluster text{fill:#333;}#mermaid-svg-8WNPwQlx3rIl8lit .cluster span{color:#333;}#mermaid-svg-8WNPwQlx3rIl8lit div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8WNPwQlx3rIl8lit .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8WNPwQlx3rIl8lit rect.text{fill:none;stroke-width:0;}#mermaid-svg-8WNPwQlx3rIl8lit .icon-shape,#mermaid-svg-8WNPwQlx3rIl8lit .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8WNPwQlx3rIl8lit .icon-shape p,#mermaid-svg-8WNPwQlx3rIl8lit .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8WNPwQlx3rIl8lit .icon-shape .label rect,#mermaid-svg-8WNPwQlx3rIl8lit .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8WNPwQlx3rIl8lit .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8WNPwQlx3rIl8lit .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8WNPwQlx3rIl8lit :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Slicer_REVISION
扩展包标识
Slicer_OS
Slicer_ARCHITECTURE
包名
CMake/SlicerExtensionCPack.cmake
服务器索引
/catalog/All/<revision>/<os>
revision-specific settings
revisionUserSettings()
配套设计:
| 机制 | 说明 |
|---|---|
| revision-specific settings | 扩展相关设置全部存在 revisionUserSettings() 里,而非全局设置。换个 Slicer 版本就是另一套扩展列表,互不污染 |
| 相对路径存储 | Modules/AdditionalPaths 用 toSlicerHomeRelativePaths() 转成相对路径存储,读取时用 toSlicerHomeAbsolutePaths() 还原。安装目录整体搬家后仍然有效 |
13. 支柱四:CMake 构建体系
13.1 扩展的唯一入口点
cmake
find_package(Slicer REQUIRED)
include(${Slicer_USE_FILE})
SlicerConfig.cmake 把主程序构建时的一切反向导出给扩展:
#mermaid-svg-bKIcXcQmAj84x1pL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bKIcXcQmAj84x1pL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bKIcXcQmAj84x1pL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bKIcXcQmAj84x1pL .error-icon{fill:#552222;}#mermaid-svg-bKIcXcQmAj84x1pL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bKIcXcQmAj84x1pL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bKIcXcQmAj84x1pL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bKIcXcQmAj84x1pL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bKIcXcQmAj84x1pL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bKIcXcQmAj84x1pL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bKIcXcQmAj84x1pL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bKIcXcQmAj84x1pL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bKIcXcQmAj84x1pL .marker.cross{stroke:#333333;}#mermaid-svg-bKIcXcQmAj84x1pL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bKIcXcQmAj84x1pL p{margin:0;}#mermaid-svg-bKIcXcQmAj84x1pL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bKIcXcQmAj84x1pL .cluster-label text{fill:#333;}#mermaid-svg-bKIcXcQmAj84x1pL .cluster-label span{color:#333;}#mermaid-svg-bKIcXcQmAj84x1pL .cluster-label span p{background-color:transparent;}#mermaid-svg-bKIcXcQmAj84x1pL .label text,#mermaid-svg-bKIcXcQmAj84x1pL span{fill:#333;color:#333;}#mermaid-svg-bKIcXcQmAj84x1pL .node rect,#mermaid-svg-bKIcXcQmAj84x1pL .node circle,#mermaid-svg-bKIcXcQmAj84x1pL .node ellipse,#mermaid-svg-bKIcXcQmAj84x1pL .node polygon,#mermaid-svg-bKIcXcQmAj84x1pL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bKIcXcQmAj84x1pL .rough-node .label text,#mermaid-svg-bKIcXcQmAj84x1pL .node .label text,#mermaid-svg-bKIcXcQmAj84x1pL .image-shape .label,#mermaid-svg-bKIcXcQmAj84x1pL .icon-shape .label{text-anchor:middle;}#mermaid-svg-bKIcXcQmAj84x1pL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bKIcXcQmAj84x1pL .rough-node .label,#mermaid-svg-bKIcXcQmAj84x1pL .node .label,#mermaid-svg-bKIcXcQmAj84x1pL .image-shape .label,#mermaid-svg-bKIcXcQmAj84x1pL .icon-shape .label{text-align:center;}#mermaid-svg-bKIcXcQmAj84x1pL .node.clickable{cursor:pointer;}#mermaid-svg-bKIcXcQmAj84x1pL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bKIcXcQmAj84x1pL .arrowheadPath{fill:#333333;}#mermaid-svg-bKIcXcQmAj84x1pL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bKIcXcQmAj84x1pL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bKIcXcQmAj84x1pL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bKIcXcQmAj84x1pL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bKIcXcQmAj84x1pL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bKIcXcQmAj84x1pL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bKIcXcQmAj84x1pL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bKIcXcQmAj84x1pL .cluster text{fill:#333;}#mermaid-svg-bKIcXcQmAj84x1pL .cluster span{color:#333;}#mermaid-svg-bKIcXcQmAj84x1pL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bKIcXcQmAj84x1pL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bKIcXcQmAj84x1pL rect.text{fill:none;stroke-width:0;}#mermaid-svg-bKIcXcQmAj84x1pL .icon-shape,#mermaid-svg-bKIcXcQmAj84x1pL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bKIcXcQmAj84x1pL .icon-shape p,#mermaid-svg-bKIcXcQmAj84x1pL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bKIcXcQmAj84x1pL .icon-shape .label rect,#mermaid-svg-bKIcXcQmAj84x1pL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bKIcXcQmAj84x1pL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bKIcXcQmAj84x1pL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bKIcXcQmAj84x1pL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Slicer 主构建
SlicerConfig.cmake
- UseSlicer.cmake
编译器设置 / C++17
所有 target 的 include 目录
宏定义与安装路径变量
Python 包装规则
构建宏:
slicerMacroBuildLoadableModule()
slicerMacroBuildScriptedModule()
SEMMacroBuildCLI()
扩展构建
扩展用的和内置模块完全一样的 CMake 宏 ------这正是
"扩展和内置模块没有本质区别"的体现。
13.2 打包与分发链路
#mermaid-svg-b6j2jb6aHiyATxhd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-b6j2jb6aHiyATxhd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-b6j2jb6aHiyATxhd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-b6j2jb6aHiyATxhd .error-icon{fill:#552222;}#mermaid-svg-b6j2jb6aHiyATxhd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b6j2jb6aHiyATxhd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-b6j2jb6aHiyATxhd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b6j2jb6aHiyATxhd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b6j2jb6aHiyATxhd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-b6j2jb6aHiyATxhd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b6j2jb6aHiyATxhd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b6j2jb6aHiyATxhd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b6j2jb6aHiyATxhd .marker.cross{stroke:#333333;}#mermaid-svg-b6j2jb6aHiyATxhd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b6j2jb6aHiyATxhd p{margin:0;}#mermaid-svg-b6j2jb6aHiyATxhd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-b6j2jb6aHiyATxhd .cluster-label text{fill:#333;}#mermaid-svg-b6j2jb6aHiyATxhd .cluster-label span{color:#333;}#mermaid-svg-b6j2jb6aHiyATxhd .cluster-label span p{background-color:transparent;}#mermaid-svg-b6j2jb6aHiyATxhd .label text,#mermaid-svg-b6j2jb6aHiyATxhd span{fill:#333;color:#333;}#mermaid-svg-b6j2jb6aHiyATxhd .node rect,#mermaid-svg-b6j2jb6aHiyATxhd .node circle,#mermaid-svg-b6j2jb6aHiyATxhd .node ellipse,#mermaid-svg-b6j2jb6aHiyATxhd .node polygon,#mermaid-svg-b6j2jb6aHiyATxhd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b6j2jb6aHiyATxhd .rough-node .label text,#mermaid-svg-b6j2jb6aHiyATxhd .node .label text,#mermaid-svg-b6j2jb6aHiyATxhd .image-shape .label,#mermaid-svg-b6j2jb6aHiyATxhd .icon-shape .label{text-anchor:middle;}#mermaid-svg-b6j2jb6aHiyATxhd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-b6j2jb6aHiyATxhd .rough-node .label,#mermaid-svg-b6j2jb6aHiyATxhd .node .label,#mermaid-svg-b6j2jb6aHiyATxhd .image-shape .label,#mermaid-svg-b6j2jb6aHiyATxhd .icon-shape .label{text-align:center;}#mermaid-svg-b6j2jb6aHiyATxhd .node.clickable{cursor:pointer;}#mermaid-svg-b6j2jb6aHiyATxhd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-b6j2jb6aHiyATxhd .arrowheadPath{fill:#333333;}#mermaid-svg-b6j2jb6aHiyATxhd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b6j2jb6aHiyATxhd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b6j2jb6aHiyATxhd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b6j2jb6aHiyATxhd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-b6j2jb6aHiyATxhd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b6j2jb6aHiyATxhd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-b6j2jb6aHiyATxhd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b6j2jb6aHiyATxhd .cluster text{fill:#333;}#mermaid-svg-b6j2jb6aHiyATxhd .cluster span{color:#333;}#mermaid-svg-b6j2jb6aHiyATxhd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b6j2jb6aHiyATxhd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-b6j2jb6aHiyATxhd rect.text{fill:none;stroke-width:0;}#mermaid-svg-b6j2jb6aHiyATxhd .icon-shape,#mermaid-svg-b6j2jb6aHiyATxhd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-b6j2jb6aHiyATxhd .icon-shape p,#mermaid-svg-b6j2jb6aHiyATxhd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-b6j2jb6aHiyATxhd .icon-shape .label rect,#mermaid-svg-b6j2jb6aHiyATxhd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-b6j2jb6aHiyATxhd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-b6j2jb6aHiyATxhd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-b6j2jb6aHiyATxhd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 驱动
扩展源码
CMake 构建
CMake/SlicerExtensionCPack.cmake
生成 CPack 包
包名含 revision / OS / arch
生成 .s4ext 描述文件
安装到 share/
Extensions/CMake/
SlicerBlockUploadExtension.cmake
扩展服务器
(Girder)
CDash 仪表板
SlicerExtensionsDashboardDriverScript.cmake
工厂机批量构建整个 Extensions Index
| 文件 | 作用 |
|---|---|
CMake/SlicerExtensionCPack.cmake |
生成 CPack 包,包名含 revision/OS/arch;同时生成 .s4ext 并安装到 share/ |
Extensions/CMake/SlicerBlockUploadExtension.cmake |
上传到扩展服务器 + CDash |
Extensions/CMake/SlicerExtensionsDashboardDriverScript.cmake |
工厂机批量构建整个 Extensions Index 的驱动脚本 |
CMake/SlicerExtensionTemplatesGenerator.cmake |
生成模板骨架,配合 Modules/Scripted/ExtensionWizard/ 模块 |
13.3 模板
模板本体在 Extensions/Testing/,共五种:
| 模板 | 用途 |
|---|---|
CLIExtensionTemplate |
CLI 模块扩展 |
LoadableExtensionTemplate |
C++ Loadable 模块扩展 |
ScriptedLoadableExtensionTemplate |
Python 模块扩展 |
ScriptedSegmentEditorEffectExtensionTemplate |
分割编辑器效果插件 |
SuperBuildExtensionTemplate |
需带第三方依赖的扩展 |
这些模板同时被当作回归测试用例------保证模板永远可编译。
14. 扩展间依赖:find_package 复用
扩展 A 想用扩展 B 的 C++ 类(如很多扩展依赖 SlicerIGT 或 Sequences):
扩展服务器 扩展 A (依赖方) SlicerExtensionGenerateConfig.cmake 扩展 B (被依赖方) 扩展服务器 扩展 A (依赖方) SlicerExtensionGenerateConfig.cmake 扩展 B (被依赖方) #mermaid-svg-v2eeR7mSf5FcI2yg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-v2eeR7mSf5FcI2yg .error-icon{fill:#552222;}#mermaid-svg-v2eeR7mSf5FcI2yg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-v2eeR7mSf5FcI2yg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-v2eeR7mSf5FcI2yg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-v2eeR7mSf5FcI2yg .marker.cross{stroke:#333333;}#mermaid-svg-v2eeR7mSf5FcI2yg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-v2eeR7mSf5FcI2yg p{margin:0;}#mermaid-svg-v2eeR7mSf5FcI2yg .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v2eeR7mSf5FcI2yg text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-v2eeR7mSf5FcI2yg .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v2eeR7mSf5FcI2yg .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-v2eeR7mSf5FcI2yg .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-v2eeR7mSf5FcI2yg .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-v2eeR7mSf5FcI2yg #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-v2eeR7mSf5FcI2yg .sequenceNumber{fill:white;}#mermaid-svg-v2eeR7mSf5FcI2yg #sequencenumber{fill:#333;}#mermaid-svg-v2eeR7mSf5FcI2yg #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-v2eeR7mSf5FcI2yg .messageText{fill:#333;stroke:none;}#mermaid-svg-v2eeR7mSf5FcI2yg .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v2eeR7mSf5FcI2yg .labelText,#mermaid-svg-v2eeR7mSf5FcI2yg .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-v2eeR7mSf5FcI2yg .loopText,#mermaid-svg-v2eeR7mSf5FcI2yg .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-v2eeR7mSf5FcI2yg .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v2eeR7mSf5FcI2yg .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-v2eeR7mSf5FcI2yg .noteText,#mermaid-svg-v2eeR7mSf5FcI2yg .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-v2eeR7mSf5FcI2yg .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v2eeR7mSf5FcI2yg .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v2eeR7mSf5FcI2yg .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v2eeR7mSf5FcI2yg .actorPopupMenu{position:absolute;}#mermaid-svg-v2eeR7mSf5FcI2yg .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-v2eeR7mSf5FcI2yg .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v2eeR7mSf5FcI2yg .actor-man circle,#mermaid-svg-v2eeR7mSf5FcI2yg line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-v2eeR7mSf5FcI2yg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 元数据 DEPENDS / build_dependencies include(${Slicer_EXTENSION_GENERATE_CONFIG}) 1 遍历全局属性 SLICER_MODULE_TARGETS SLICER_MODULE_LOGIC_TARGETS SLICER_MODULE_MRML_TARGETS SLICER_MODULE_WIDGET_TARGETS SLICER_WRAP_HIERARCHY_TARGETS 2 生成 BConfig.cmake 3 find_package(B REQUIRED) 4 导出 include 目录 + target 名 5 installExtension(..., installDependencies=true) 自动连带安装依赖 6
依赖方的写法:
cmake
find_package(Sequences REQUIRED)
set(MODULE_INCLUDE_DIRECTORIES ${vtkSlicerSequencesModuleMRML_INCLUDE_DIRS})
set(MODULE_TARGET_LIBRARIES vtkSlicerSequencesModuleMRML)
依赖关系同时声明在元数据的 DEPENDS / build_dependencies 字段里,
服务器据此在运行时自动连带安装依赖
(installExtension(..., installDependencies = true),
见 Base/QTCore/qSlicerExtensionsManagerModel.h:298)。
注意:若 B 未生成
BConfig.cmake,
SlicerBlockAdditionalLauncherSettings.cmake会给出明确的 WARNING 提示。
15. 支柱五:元数据与服务器
15.1 两种格式并存
| 格式 | 说明 |
|---|---|
.s4ext |
老的 key-value 描述文件,由 Extensions/CMake/SlicerFunctionGenerateExtensionDescription.cmake 生成,随包安装。是本地判断"某扩展已安装"的凭据:extensionDescriptionFile() 返回 <installPath>.s4ext |
| JSON catalog entry | 新格式,提交到 ExtensionsIndex 仓库的就是这个。正式 schema:Schemas/slicer-extension-catalog-entry-schema-v1.0.1.json |
字段定义在 Extensions/CMake/SlicerExtensionDescriptionSpec.cmake:
| 字段 | 必填 | 说明 |
|---|---|---|
SCM |
是 | 源码管理系统(git) |
SCMURL |
是 | 只读 checkout URL |
SCMREVISION |
是 | 版本号 |
DEPENDS |
否 | 空格分隔的依赖扩展列表,无依赖时值为 NA |
CATEGORY / ICONURL / SCREENSHOTURLS / HOMEPAGE / CONTRIBUTORS / DESCRIPTION / STATUS / ENABLED / BUILD_SUBDIRECTORY |
否 | 展示与构建元信息 |
JSON 模板示例(Extensions/LoadableExtensionTemplate.json):
json
{
"$schema": "https://raw.githubusercontent.com/Slicer/Slicer/main/Schemas/slicer-extension-catalog-entry-schema-v1.0.0.json#",
"build_dependencies": [],
"build_subdirectory": ".",
"category": "Examples",
"scm_type": "local",
"scm_revision": "",
"scm_url": "Testing/LoadableExtensionTemplate"
}
15.2 服务器交互
全部封装在 Base/QTCore/qSlicerExtensionsManagerModel.cxx
(约 13 万字符的单文件,是这块的绝对核心):
| 组件 | 职责 |
|---|---|
qSlicerExtensionsManagerModel |
模型、状态机、服务器 API(Girder v1)、安装 / 卸载事务 |
Base/QTCore/qSlicerExtensionDownloadTask.h |
异步下载任务 |
| LibArchive | 解压 |
Base/QTGUI/qSlicerExtensionsManagerDialog.cxx |
管理器对话框 |
Base/QTGUI/qSlicerExtensionsServerWidget.cxx |
服务器端扩展浏览 |
Base/QTGUI/qSlicerExtensionsLocalWidget.cxx |
本地已装扩展列表 |
Base/QTCore/qSlicerApplicationUpdateManager.cxx |
应用自身的更新检查 |
16. 生命周期状态机
Model 里每个扩展维护多个正交 状态位
(InstalledColumn / LoadedColumn / BookmarkedColumn / scheduled 标记):
#mermaid-svg-Tc6kVcVUL1VR5Vcd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .error-icon{fill:#552222;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .marker.cross{stroke:#333333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd p{margin:0;}#mermaid-svg-Tc6kVcVUL1VR5Vcd defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-Tc6kVcVUL1VR5Vcd g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-Tc6kVcVUL1VR5Vcd g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Tc6kVcVUL1VR5Vcd .edgeLabel .label text{fill:#333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .label div .edgeLabel{color:#333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd #statediagram-barbEnd{fill:#333333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .cluster-label,#mermaid-svg-Tc6kVcVUL1VR5Vcd .nodeLabel{color:#131300;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .note-edge{stroke-dasharray:5;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-note text{fill:black;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram-note .nodeLabel{color:black;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagram .edgeLabel{color:red;}#mermaid-svg-Tc6kVcVUL1VR5Vcd #dependencyStart,#mermaid-svg-Tc6kVcVUL1VR5Vcd #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-Tc6kVcVUL1VR5Vcd .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Tc6kVcVUL1VR5Vcd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务器上可见
downloadAndInstallExtension()
aboutToLoadExtensions()
(仅在启动早期快照一次)
scheduleExtensionForUninstall()
scheduleExtensionForUpdate()
重启后
uninstallScheduledExtensions()
重启后
updateScheduledExtensions()
cancelExtensionScheduledForUninstall()
cancelExtensionScheduledForUpdate()
Available
Installed
Loaded
ScheduledUninstall
ScheduledUpdate
Bookmarked 是正交状态位
可与任意状态并存
为什么 Installed 与 Loaded 必须分开
qSlicerExtensionsManagerModel.cxx:3407 的 aboutToLoadExtensions()
由 Base/QTCore/qSlicerCoreApplication.cxx:560 在启动早期调用:
cpp
// Extensions that are currently installed are about to be loaded, so set
// "loaded" flag based on current "installed" flag.
// We may install extensions later, but they will not be loaded immediately
// (only on the next application restart).
d->LoadedExtensions.clear();
for (int rowIdx = 0; rowIdx < d->Model.rowCount(); ++rowIdx)
{
bool installed = /* InstalledRole */;
loadedItem->setData(installed, LoadedRole);
...
}
它把当时的 Installed 状态冻结 为 Loaded。之后本次会话内再装的扩展,
UI 上会显示"已安装但未加载",提示需要重启。
GUI 层 Base/QTGUI/qSlicerExtensionsManagerDialog.cxx:76 也只靠比对
Modules/AdditionalPaths 前后是否变化来决定要不要弹"重启"提示。
17. 完整安装流程(运行时)
QSettings (revision-specific) 文件系统 扩展服务器 (Girder v1) qSlicerExtensionDownloadTask qSlicerExtensionsManagerModel ExtensionsServerWidget QSettings (revision-specific) 文件系统 扩展服务器 (Girder v1) qSlicerExtensionDownloadTask qSlicerExtensionsManagerModel ExtensionsServerWidget #mermaid-svg-quWbz1OzFzwxbVDY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-quWbz1OzFzwxbVDY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-quWbz1OzFzwxbVDY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-quWbz1OzFzwxbVDY .error-icon{fill:#552222;}#mermaid-svg-quWbz1OzFzwxbVDY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-quWbz1OzFzwxbVDY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-quWbz1OzFzwxbVDY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-quWbz1OzFzwxbVDY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-quWbz1OzFzwxbVDY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-quWbz1OzFzwxbVDY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-quWbz1OzFzwxbVDY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-quWbz1OzFzwxbVDY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-quWbz1OzFzwxbVDY .marker.cross{stroke:#333333;}#mermaid-svg-quWbz1OzFzwxbVDY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-quWbz1OzFzwxbVDY p{margin:0;}#mermaid-svg-quWbz1OzFzwxbVDY .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-quWbz1OzFzwxbVDY text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-quWbz1OzFzwxbVDY .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-quWbz1OzFzwxbVDY .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-quWbz1OzFzwxbVDY .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-quWbz1OzFzwxbVDY .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-quWbz1OzFzwxbVDY #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-quWbz1OzFzwxbVDY .sequenceNumber{fill:white;}#mermaid-svg-quWbz1OzFzwxbVDY #sequencenumber{fill:#333;}#mermaid-svg-quWbz1OzFzwxbVDY #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-quWbz1OzFzwxbVDY .messageText{fill:#333;stroke:none;}#mermaid-svg-quWbz1OzFzwxbVDY .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-quWbz1OzFzwxbVDY .labelText,#mermaid-svg-quWbz1OzFzwxbVDY .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-quWbz1OzFzwxbVDY .loopText,#mermaid-svg-quWbz1OzFzwxbVDY .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-quWbz1OzFzwxbVDY .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-quWbz1OzFzwxbVDY .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-quWbz1OzFzwxbVDY .noteText,#mermaid-svg-quWbz1OzFzwxbVDY .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-quWbz1OzFzwxbVDY .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-quWbz1OzFzwxbVDY .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-quWbz1OzFzwxbVDY .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-quWbz1OzFzwxbVDY .actorPopupMenu{position:absolute;}#mermaid-svg-quWbz1OzFzwxbVDY .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-quWbz1OzFzwxbVDY .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-quWbz1OzFzwxbVDY .actor-man circle,#mermaid-svg-quWbz1OzFzwxbVDY line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-quWbz1OzFzwxbVDY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户 点击 "Install" 1 downloadAndInstallExtension(extensionId) 2 GET /catalog/All/<revision>/<os> 3 元数据(含 DEPENDS) 4 解析依赖,递归安装依赖项 5 启动异步下载 6 下载扩展包 7 归档文件 8 downloadFinished 9 LibArchive 解压到 <ExtInstallPath>/<ExtName>/ 10 写入 <ExtName>.s4ext 11 extensionModulePaths() 计算 4 个子路径 12 追加到 Modules/AdditionalPaths (转相对路径) 13 extensionInstalled 信号 14 标记为 Installed(未 Loaded)→ 提示重启 15 用户
18. 第二部分小结
| 层次 | 承担的职责 | 关键实现 |
|---|---|---|
| 加载 | 目录约定 + Modules/AdditionalPaths |
模块工厂(复用内置模块机制) |
| 运行环境 | 库路径 / PYTHONPATH 注入 | CTK AppLauncher + AdditionalLauncherSettings.ini |
| 二进制安全 | revision × OS × arch 硬绑定 | SlicerExtensionCPack + revision-specific settings |
| 构建 | SlicerConfig.cmake 导出全部构建上下文 |
与内置模块同一套 CMake 宏 |
| 依赖 | 扩展间 find_package |
SlicerExtensionGenerateConfig + DEPENDS 元数据 |
| 分发 | .s4ext / JSON catalog + Girder API |
qSlicerExtensionsManagerModel |
设计上最值得注意的一点
Slicer 没有为扩展发明任何新的插件加载技术。
它只是把"模块搜索路径"这个变量暴露出来,剩下的全是打包、版本绑定和分发的工程问题。
这也是为什么一个内置模块可以几乎零改动地拆成扩展,反之亦然。
附录
附录 A:关键文件速查
A.1 扩展机制
| 文件 | 说明 |
|---|---|
Base/QTCore/qSlicerExtensionsManagerModel.{h,cxx} |
扩展机制绝对核心,约 13 万字符 |
Base/QTCore/qSlicerExtensionDownloadTask.{h,cxx} |
异步下载 |
Base/QTCore/qSlicerApplicationUpdateManager.{h,cxx} |
应用自身更新 |
Base/QTCore/qSlicerLoadableModuleFactory.cxx |
C++ 模块发现 |
Base/QTGUI/qSlicerScriptedLoadableModuleFactory.cxx |
Python 模块发现 |
Base/QTGUI/qSlicerExtensionsManagerDialog.cxx |
管理器对话框、重启判定 |
Base/QTGUI/qSlicerExtensionsServerWidget.cxx |
服务器扩展浏览 |
Base/QTGUI/qSlicerExtensionsLocalWidget.cxx |
本地扩展列表 |
Base/QTGUI/qSlicerSettingsExtensionsPanel.cxx |
扩展设置面板 |
CMake/SlicerExtensionCPack.cmake |
打包 |
CMake/SlicerExtensionGenerateConfig.cmake |
扩展间依赖导出 |
CMake/SlicerExtensionConfig.cmake.in |
Config 模板 |
CMake/SlicerExtensionTemplatesGenerator.cmake |
模板生成 |
CMake/SlicerBlockInstallExtensionPackages.cmake |
内置扩展打包进主程序 |
Extensions/CMake/SlicerExtensionDescriptionSpec.cmake |
元数据字段定义 |
Extensions/CMake/SlicerFunctionGenerateExtensionDescription.cmake |
.s4ext 生成 |
Extensions/CMake/SlicerBlockAdditionalLauncherSettings.cmake |
Launcher 环境注入 |
Extensions/CMake/SlicerBlockUploadExtension.cmake |
上传 |
Extensions/CMake/SlicerExtensionsDashboardDriverScript.cmake |
批量构建驱动 |
Schemas/slicer-extension-catalog-entry-schema-v1.0.1.json |
JSON 元数据 schema |
Modules/Scripted/ExtensionWizard/ |
扩展创建向导(Python 模块) |
Extensions/Testing/ |
五种扩展模板 + 回归测试 |
A.2 核心框架
| 文件 | 说明 |
|---|---|
Libs/MRML/Core/vtkMRMLScene.{h,cxx} |
场景容器,应用状态的唯一真相源 |
Libs/MRML/Core/vtkMRMLNode.{h,cxx} |
所有节点基类 |
Libs/MRML/Core/vtkMRMLDisplayableNode.h |
Data 节点基类 |
Libs/MRML/Core/vtkMRMLDisplayNode.h |
Display 节点基类 |
Libs/MRML/Core/vtkMRMLStorageNode.h |
Storage 节点基类 |
Libs/MRML/Core/vtkMRMLTransformNode.h |
变换节点 |
Libs/MRML/DisplayableManager/ |
MRML → VTK Actor 桥梁 |
Base/Logic/vtkSlicerApplicationLogic.{h,cxx} |
应用级逻辑、后台任务队列 |
Base/Logic/vtkSlicerModuleLogic.h |
所有模块 Logic 基类 |
Base/QTCore/qSlicerCoreApplication.{h,cxx} |
无 GUI 应用内核 |
Base/QTCore/qSlicerCoreIOManager.{h,cxx} |
Reader/Writer 注册表 |
Base/QTCore/qSlicerModuleFactoryManager.{h,cxx} |
模块工厂管理 |
Base/QTGUI/qSlicerApplication.{h,cxx} |
GUI 应用内核 |
Base/QTGUI/qSlicerLayoutManager.{h,cxx} |
视图布局管理 |
Base/QTGUI/qSlicerAbstractModuleWidget.h |
模块界面基类 |
附录 B:关键数字
| 项 | 数量 |
|---|---|
vtkMRML*Node 类(Libs/MRML/Core/) |
88 |
Modules/Loadable/ 子目录 |
22 |
Modules/Scripted/ 子目录 |
17(含 DICOMLib / DICOMPlugins 两个支持库,非独立模块) |
Modules/CLI/ 子目录 |
38 |
| 扩展模板种类 | 5 |
SuperBuild/External_*.cmake |
37 |
附录 C:可继续深入的方向
- 写一个扩展的完整实操路径(从 ExtensionWizard 到上架 Extensions Index)
qSlicerExtensionsManagerModel的下载安装事务细节(含失败回滚)- SuperBuild 型扩展如何携带第三方依赖打包
- DisplayableManager 的渲染更新机制细节
- MRML 场景的 Undo/Redo 与 Import/Export 实现
vtkSlicerApplicationLogic的后台任务队列与线程模型- SlicerExecutionModel 如何从 XML 生成 CLI 模块的 GUI