第 27 章:阅读官方示例、API 与源码

学习目标

教程只能覆盖一部分对象。遇到新的 Reader、Filter 或渲染参数时,你需要能自己找到答案,并分辨“文档说明的意图”和“当前安装版本的实际行为”。本章给出一条可重复的查阅路线,带你用第 25 章的数据更新问题和第 23 章的按需加载问题作两次完整追踪。

27.1 从一个可验证的问题开始

不要以“把 vtk.js 源码看一遍”为目标。先把疑问缩小成一次调用:

每个问题都写明安装版本、输入对象、调用顺序和观察值。例如“37.4.2 中,单分量 vtkImageData 的 Point Data 标量直接原位改值后,iso.getOutputData() 的单元数不变”,比“更新有问题”更容易查。源码阅读的终点是能用小例子验证一个具体结论,而不是记住整个类文件。

27.2 四类资料各做什么

资料 适合回答 使用时注意
vtk.js 官方示例 对象组合、渲染 Profile、最小显示流程 示例可能使用不同版本或旧构建方式;先看数据来源和输入类型
vtk.js 官方 API 类、方法名、参数和概念说明 在线页面通常跟随站点发布节奏,不一定等于你的安装版本
Kitware/vtk-js 源码仓库 实现、默认值、特殊分支、更新条件 浏览时确认 tag/commit;默认分支可能比本教程更新
本地安装包 .d.ts 与 .js 当前 37.4.2 的声明和实际执行代码 声明可能有遗漏或旧注释;运行行为以实测和实现为准

本教程的所有示例固定 @kitware/vtk.js 37.4.2。先看项目 package.json 和锁文件,确认真正安装的版本,然后在 examples/stage-08/node_modules/@kitware/vtk.js/ 中查同版本代码。若你正在维护不同版本的项目,改用该项目自己的安装包。官方网页、搜索结果和问答文章很有帮助,但不要拿最新页面直接断言旧项目的行为。

27.3 读示例时追五条线

打开一个官方示例,不必逐行先读 DOM 和 CSS。优先标记这五条线:

  1. 导入与 Profile:是否先导入 Rendering/Profiles/Geometry、Volume 或 All?示例的 import 路径与你安装的包名是否一致?
  2. 数据来源:是 vtkSphereSource 生成、Reader 下载,还是 JavaScript 直接构造?输入究竟是 vtkPolyData 还是 vtkImageData?
  3. 算法连接:哪里调用 setInputData,哪里调用 setInputConnection(...getOutputPort())?是否有 Filter 改变数据类型?
  4. 显示连接:Mapper、Actor/Volume、Renderer、相机和 renderWindow.render() 怎样串起来?
  5. 交互更新:控件事件修改的是数据、Filter 参数、Mapper、Property 还是 Camera?之后是否等待异步操作、请求输出和重新绘制?

把代码缩成一张流图,例如:

HttpDataSetReader → ImageData → ImageMarchingCubes → PolyData → Mapper → Actor

这样先知道每个对象负责什么,再研究细节。若示例用了你不认识的对象,先查该类的输入和输出类型,再复制调用。看似相似的 vtkMapper、vtkImageMapper 与 vtkVolumeMapper 不能只凭名字替换。

27.4 读 API 时抓住四个问题

查某个对象时依次找:构造方法、输入/输出类型、默认值、修改后怎样生效。例如 vtkClipPolyData:构造 newInstance({clipFunction: plane});输入和输出都是 vtkPolyData;insideOut 的默认值影响保留侧;平面移动后要从输出端口取新结果并重新绘制。

类型声明很适合快速发现方法名,但并非万无一失。一个具体例子是本教程版本的 IO/Core/HttpDataSetReader.d.ts 把 loadData() 返回类型写成 string,而对应 .js 实现返回 Promise,第 7 阶段示例也以 await reader.loadData() 成功运行。遇到这样的冲突,记录版本和复现,优先核对实现与运行结果;不要根据一条有误声明删掉必要的 await。

27.5 源码定位地图:高频对象从哪里找

安装依赖后,以项目的 node_modules/@kitware/vtk.js/ 为根:

你要追的对象 安装包中的典型位置 首先找什么
vtkDataArray Common/Core/DataArray.js 与 .d.ts getData、getRange、dataChange、setData
vtkImageData / vtkDataSet Common/DataModel/ImageData.js、DataSet.js extent、spacing、getMTime
vtkHttpDataSetReader IO/Core/HttpDataSetReader.js setUrl、getArrays、enableArray、loadData
vtkImageMarchingCubes Filters/General/ImageMarchingCubes.js requestData、输入 scalars、输出 PolyData
vtkClipPolyData Filters/Core/ClipPolyData.js 隐式函数、insideOut、输出端口
vtkVolumeMapper Rendering/Core/VolumeMapper.js、对应 WebGL 实现 采样设置与渲染实现的分工
通用数据管线 macros.js modified、getMTime、algo、shouldUpdate、getOutputData

许多类的方法由 macros.obj、macros.algo、macros.setGet 等宏装配出来,因此在类文件中搜不到 getOutputPort、setInputData 并不表示方法不存在。若类文件里看到 macros.algo(publicAPI, model, 1, 1),继续去 macros.js 查通用输入、输出和更新逻辑。安装包是发布后的 .js;GitHub 的 Sources/.../index.js 路径与发布包布局可能不同,找文件时不要假设二者目录逐层一致。

在终端里可以先按文件名定位,再搜索方法,不必一次打印整棵目录:

rg --files node_modules/@kitware/vtk.js | rg 'DataArray|ImageMarchingCubes|macros'
rg -n 'dataChange|getRange|shouldUpdate' node_modules/@kitware/vtk.js/Common/Core/DataArray.js node_modules/@kitware/vtk.js/macros.js

这些命令需要在装有依赖的示例项目目录下运行。若只想看某个类是否存在,先看 import 能否解析和 .d.ts 文件,再深挖实现。

27.6 实战追踪一:为什么原位改值后画面不变

从第 25 章的问题出发,按这样的顺序查:

  1. 在 DataArray.js 找 getData():它给出 TypedArray 或其中的有效视图。对这个数组直接赋值,不会自动调用对象方法。
  2. 找 getRange():范围可缓存。再找 dataChange():它清空 ranges 并调用 modified()。
  3. 在 DataSetAttributes/FieldData.js 与 DataSet.js 找 getMTime():父对象的查询结果会考虑子数组的较新修改序号。
  4. 在 macros.js 的 algo 中找 shouldUpdate() 和 getOutputData():输出与输入的修改序号决定是否再执行算法。
  5. 回到最小示例,分别在赋值前、只赋值后、调用 dataChange() 后打印原始值、getRange()、ImageData getMTime() 和等值面单元数。

这样能定位“值已变、范围没变、Filter 未重算”的具体断点。第 8 阶段示例把这五个观测值直接放在页面上;只改 Actor 颜色时,等值面输出序号不会因此改变。

27.7 实战追踪二:Reader 为何只读到清单

从 reader.setUrl('/sample/index.json',{loadData:false}) 出发:

  1. 看 HttpDataSetReader.d.ts 的 setUrl、getArrays、enableArray、loadData,理解公开入口。
  2. 看 HttpDataSetReader.js 的 setUrl、updateMetadata 与 processDataSet:清单被解析为数据对象,属性数组先被登记。
  3. 看 enableArray:它只切换数组选择状态;再看 loadData:它找已启用、仍有 ref 的数组并请求二进制文件。
  4. 看 IO/Core/DataAccessHelper/LiteHttpDataAccessHelper.js 的 fetchArray:baseURL/ref.basepath/ref.id 形成请求地址,返回数据按 dataType 和字节序解释。
  5. 用浏览器 Network 验证清单请求和两个数组请求的实际时机。不能只因 getArrays() 列出名字,就断言数值已经下载。

这套查法也适用于新 Reader:先找公开方法,再找网络/解析分支,最后用真实请求和输出对象证实。若某个方法的注释与实现冲突,应把两者和版本都记下来,并写一个最小验证例子。

27.8 建立自己的小型对象笔记

学完一个高频对象,只记录五件事:输入类型、输出类型、关键方法、默认值、一个已经验证的坑。例如:

vtkImageMarchingCubes
输入:vtkImageData,active Point Data 单分量标量
输出:vtkPolyData
方法:setContourValue、setInputData、getOutputPort
注意:阈值超出范围可能得到空网格;换数组后要检查更新

笔记旁附上项目版本、官方 API 链接、源码位置和你的最小示例。以后升级 vtk.js,先运行示例并重新核对这些记录。能追溯的短笔记比大量没有版本标记的复制代码更可靠。

本章小结

自学 vtk.js 的顺序可以固定为:提出可验证问题 → 看官方示例建立对象关系 → 查 API 和类型声明 → 读同版本实现与通用宏 → 用最小程序观察数据和画面。第 8 阶段到此结束;下一阶段会把这些方法用于表面查看器、体数据查看器和按需加载的综合项目。