【Javascript VTK】在页面中放置VTK三维模型

2023-11-07

一、问题描述

在项目的开发过程中,遇到将vtk三维重建结果放置到网页(Web Page)中进行可视化展示的棘手问题,想要实现的效果图如下:

图一:最终实现的 v t k − 3 D 模型 W e b 页面可视化 图一:最终实现的vtk-3D模型Web页面可视化 图一:最终实现的vtk3D模型Web页面可视化

笔者调研了很多前者的博客资料等相关文献,发现了很多问题,大致梳理如下:

  • 文章潦草:大多数的解决方案都不够详细,对于后面的学习者复刻难度较高,存在部分漏洞、对于萌新不友好。
  • 需求存在偏差:前人博客实现的目标与我们的目标存在差距,大多数是提供让vtk三维建模图像放置在整个页面的解决方案,而我们项目的需求是在网页中的特定窗口放置三维建模结果。从需求侧的层面出发,并没有相似内容可供参考。
  • 环境配置问题介绍不够详尽:许多博主对于vtk.js的相关配置几笔带过,并且不同博主有不同的项目配置方案,有的甚至没有详写如何配置vtk.js环境。对于没有使用过vtk的Javascript库的萌新并不是特别友好,这将会花费很多时间在配置环境上。

基于上述问题,笔者决定记录开发过程中的解决方案,力求清晰、简明、切中肯綮。希望帮助到更多同行开发者进行vtk的Web可视化工作,节省时间。

二、环境配置

0. 开发环境和背景

前端素材库

前端采用的是bootstrap库用作常规页面开发与渲染,bootstrap提供的素材库、javascriptcss对于并不熟悉前端领域的开发者非常友好。

如果您想了解bootstrap库的相关内容,您可以访问官方文档(中文手册)

后端框架

这篇博客使用的例程,是基于python flask后端框架开发的VTK Web可视化页面,因此有最基本的flask项目结构和项目文件夹,也就是:

  • static文件夹,存放静态资源文件,如javascript脚本和css文件
  • templates文件夹,存放html文件
  • app.py执行脚本

1. 项目结构

具体的项目结构如下图所示:

图二:项目具体结构 图二:项目具体结构 图二:项目具体结构

其中,我们的vtk三维重建结果被保存为tttest.vtp文件,存放在static/ctpModels/路径下,在之后的实现代码中,我们会对此路径进行提取。

2. 前后端分别创建项目方案的环境配置

笔者的开发方向并不是前端领域,因此对于相关术语的描述与使用并不是很熟悉,见谅!

之前调研过的前后端分别创建项目的启动方式,即:

  • 前端:使用npm start指令启动项目(Vue等)
  • 后端:使用python app.py指令启动后端项目

这样的前后端分离启动项目笔者并不熟悉,并且整个项目也比较heavy。您可以参阅这篇博客来获取以这种方式搭建vtk.js环境的详细步骤。

笔者采取的是比较轻量化的启动方案:

3. 简单调用的轻量级方案的环境配置

简单使用: <script src='https://unpkg.com/vtk.js'></script>来载入vtk.js配置。

当网页开始载入时,浏览器会自动请求https://unpkg.com/vtk.js这个地址,并将其Javascript脚本导入,这个过程需要联网操作。

如果您想节省用户所承担的网络/时间成本,您可以自行提取该页面上的javascript脚本,将其存放在static/js/目录下,再进行本地导入操作。

三、代码实现与详细讲解

1. 完整代码

<!-- 用于进行三维模型可视化的页面小窗口展示 -->
  <section>
    <div class="container">
      <div class="row align-items-center justify-content-between">
        <div class="col-md text-center">
          <div id="previewwww" style="left: 30px; height: 512px; width: 512px;">
          </div>
        </div>
        <div class="col-md">
          <div>
            <p>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Vero nam sint quibusdam
              consequatur qui fuga praesentium, dolor magni quisquam ducimus ea adipisci error
              eaque explicabo similique ab? Suscipit magni nemo voluptatibus itaque accusantium
              exercitationem deserunt et quisquam qui obcaecati ut non incidunt ullam ad,
              laboriosam hic optio, excepturi aliquam quae ipsum tempore sit molestias quod!
              Tempora asperiores similique ab ipsa at fugit cumque sed beatae eaque nemo doloribus
              omnis deserunt illo eos quos, temporibus reprehenderit maxime blanditiis debitis
              voluptates. Deserunt velit dolorem ab odit totam? Itaque, iste? Facere repudiandae,
              unde doloribus id similique illum rem consectetur necessitatibus impedit laudantium
              asperiores natus harum enim autem incidunt recusandae quas, quia architecto at debitis
              , fugit dignissimos aspernatur? Reprehenderit, pariatur nulla ex tempore, voluptatum
              tempora minus fugit nobis provident repudiandae expedita enim debitis optio alias
              quidem distinctio ad error, nemo aliquid! Saepe placeat optio nulla excepturi qui
              repellat doloribus. Reiciendis perferendis incidunt unde nulla!</p>
          </div>
        </div>
      </div>
    </div>


    <!-- 载入vtk.js脚本 -->
    <script src='https://unpkg.com/vtk.js'></script>
    
    <!-- 载入展示三尾重建的脚本 -->
    <script>
        // Load script from https://unpkg.com/vtk.js then...
        var myContainer = document.querySelector('#previewwww');
        var fullScreenRenderer = vtk.Rendering.Misc.vtkFullScreenRenderWindow.newInstance({
            background: [1.0, 1.0, 1.0],
            container: myContainer,
        });

        // render configuration
        var renderer = fullScreenRenderer.getRenderer();
        var renderWindow = fullScreenRenderer.getRenderWindow();

        var actor = vtk.Rendering.Core.vtkActor.newInstance();
        var mapper = vtk.Rendering.Core.vtkMapper.newInstance();

        const vtpReader = vtk.IO.XML.vtkXMLPolyDataReader.newInstance();

        mapper.setInputConnection(vtpReader.getOutputPort());
        actor.setMapper(mapper);

        mapper.setScalarVisibility(false);
        actor.getProperty().setColor(1.0, 1.0, 1.0);

        function beginRender() {
            renderer.addActor(actor);
            renderer.resetCamera();
            renderWindow.render();
        }

        vtpReader.setUrl("{{ url_for('static', filename='vtpModels/tttest.vtp') }}").then(beginRender);
    </script>
  </section>

2. 第一部分:进行基本的html架构搭建

vtk-js的可视化是在html页面元素中的一个div中进行的展示,首先要进行最基本的html页面元素布局。我们将上面的代码逐步拆减、提取骨干:

<!-- 用作例程基本的页面布局 -->
<!-- 一行两列,最外层用一个container包装 -->
  <div class="container">
    <!-- 先写row,里面再写col -->
    <!-- 结合bootstrap配置好相关的类,调整位置 -->
    <div class="row align-items-center justify-content-between">
      <!-- 第一个column,放置三维建模窗口 -->
      <div class="col-md text-center">
        <!-- 这里要设置好div的id,便于后面进行绑定操作 -->
        <div id="previewwww" style="left: 30px; height: 512px; width: 512px;">
        </div>
      </div>
      <!-- 第二个column,放置解说文字 -->
      <div class="col-md">
        <div>
          <p>Lorem100</p>
        </div>
      </div>
    </div>
  </div>

这段代码要实现的效果:

图三: h t m l 实现的页面骨架布局 图三:html实现的页面骨架布局 图三:html实现的页面骨架布局

其中,值得注意的是:一定要给需要进行3D展示的窗口提供id标注,以便在后续的javascript代码中通过document.querySelector()进行查找和绑定,从而支持后续的动态展示代码运行。

同样,笔者在调整css页面布局上花费了很多时间。您可以照搬示例代码中的css信息,也可以自行设置。有关css调整的问题,会在后面详细讲到。

3. 第二部分:导入vtk.js,为下面自己的js做铺垫

<!-- 以下是更加宏观的三个部分代码的骨架 -->

<!-- 上面提到的第一部分的基本html搭建 -->
<div class="container">
  <!-- your code here... -->
</div>

<!-- 载入vtk.js脚本 -->
<script src='https://unpkg.com/vtk.js'></script>

<!-- 载入展示三尾重建的脚本 -->
<script>
  // your code here...
</script>

三个部分的代码骨架非常清晰,需要注意的是,要在您自己的javascript代码之前插入vtk.js脚本。

4. 第三部分:编写可视化的javascript脚本

<!-- 载入展示三尾重建的脚本 -->
<script>
    // 查找之前定义的用于可视化的div
    var myContainer = document.querySelector('#previewwww');

    // 用于页面可视化的Renderer,官方文档介绍是使用fullScreenRenderer
    var fullScreenRenderer = vtk.Rendering.Misc.vtkFullScreenRenderWindow.newInstance({
        background: [1.0, 1.0, 1.0],  //设置背景颜色
        container: myContainer,  // 绑定myContainer
    });

    // 获取渲染器和渲染窗口
    var renderer = fullScreenRenderer.getRenderer();
    var renderWindow = fullScreenRenderer.getRenderWindow();

    // 定义actor和mapper,有关actor和mapper的相关内容,您可以查找vtk官方文档或者其他资料
    var actor = vtk.Rendering.Core.vtkActor.newInstance();
    var mapper = vtk.Rendering.Core.vtkMapper.newInstance();

    // 定义vtpReader,读取您自己的.vtp文件
    const vtpReader = vtk.IO.XML.vtkXMLPolyDataReader.newInstance();

    // 进行inputConnection连接
    mapper.setInputConnection(vtpReader.getOutputPort());
    actor.setMapper(mapper);

    // 调整模型的颜色
    mapper.setScalarVisibility(false);
    actor.getProperty().setColor(1.0, 1.0, 1.0);

    // 设置渲染函数
    function beginRender() {
        renderer.addActor(actor);
        renderer.resetCamera();
        renderWindow.render();
    }

    // 寻找模型存放的位置并开始渲染
    vtpReader.setUrl("your/.vtp model/path").then(beginRender);
</script>

上述代码中,所有相关函数及其功能已经一一注释,在这里需要详细讲解笔者开发过程中踩过的坑,请您一定耐心阅读。


4.1 从fullScreenRenderer的初始化认识vtk.js

var fullScreenRenderer = vtk.Rendering.Misc.vtkFullScreenRenderWindow.newInstance()函数中,我们可以看到传入的参数是一个字典类,在字典中进行初始化的设置。其中,background: [1.0, 1.0, 1.0]是将背景设置为白色,您可以调整为[0, 0, 0]以将其变为黑色,或者通过调整这个列表的值,以获取不同的背景颜色。

4.2 vtpReader

vtp其实是Visualization Toolkit Polygonal Data的缩写,也是较为通用的一份文件,通过vtk.IO.XML.vtkXMLPolyDataReader来读取相应文件。

从本地读取.vtp模型,需要借助vtk.js自带的工具:vtk.IO.XML.vtkXMLPolyDataReader来实现读取操作,在这里我们首先定义一个vtpReader,我们会在最后通过调用vtpReader.setUrl()的方式读取本地的三维模型文件。

需要注意的是:

mapper.setInputConnection(vtpReader.getOutputPort());
actor.setMapper(mapper);

这两句代码的功能仅仅是建立vtpReader的Output Port连接,而不是读取文件。读取文件的操作会在脚本的最后执行。这样就更能理解在读取文件之前就进行mapper、actor和vtpReader操作的原因。

4.3 设置模型颜色

笔者最初进行开发的时候,读取到的模型颜色是蓝色的。

图四:两种不同的模型颜色 图四:两种不同的模型颜色 图四:两种不同的模型颜色

默认读取出来的模型颜色就是蓝色,为了获取更好的视觉效果,我们可以通过下面两句javascript代码改变模型颜色:

mapper.setScalarVisibility(false);
actor.getProperty().setColor(1.0, 1.0, 1.0);

需要注意的是,第一句mapper.setScalarVisibility(false);一定要写,否则可能无法改变颜色,因为mapper映射器的scalarVisibility属性的颜色设置为false,会禁用禁用标量可见性,并使参与者的颜色独立于数据值。

4.4 读取模型

笔者的模型预先处理好,已经放置在本地的文件夹中,通过vtpReader.setUrl()读取。

您可以自行选择路径,并且进行模型的读取操作。

注:有关vtpReader的setUrl()读取文件失败的问题,您可以参阅博客后面的部分,有详细提到。


四、开发过程中可能遇到的问题

1. 两种部署方式有什么不同?

前者较为heavy,应该是需要用到vue等大型前端项目才会使用的部署方案,优点是开发方便(编写代码之类),但是对于普通的小项目或者对前端不熟的开发者来说未免太过麻烦。

笔者推荐轻量级部署方案,仅仅是实现一个功能并不需要大费周章,简单部署导入vtk.js运行即可。

2. 本地做html开发vtk三维Web可视化失败

在开发过程中,您在浏览器中可以通过F12打开后台查看代码运行的问题,笔者曾经在使用vtpReader.setUrl()过程中遇到这样的报错:

Access to XMLHttpRequest at 'file:///C:/Users/27813/Desktop/vtk-jscone-resolution-example/dist/tttest.vtp' 
from origin 'null' has been blocked by CORS policy: Cross origin requests are only 
supported for protocol schemes: http, data, isolated-app, chrome-extension, 
chrome, https, chrome-untrusted.

出现这样的错误信息,是因为正在尝试使用文件URL从本地文件系统加载VTP文件,但由于CORS(跨源资源共享)策略,您的浏览器正在阻止该请求。这是因为文件URL不是跨源请求所允许的协议方案之一。

也就是说,如果您的开发环境的项目结构是简单的html和一份.vtp模型文件,很有可能会失败,因为并没有遵守改协议,笔者最初也是在这里耗费许久时间。

为了解决这个问题,您可以尝试在Web运行中直接进行开发。笔者在后续的开发中,直接将flask调成debug模式,然后将网页在本地运行起来,直接在项目中进行代码开发,可以有效解决这个问题。

同时,笔者使用了flask框架自带的url_for()函数,解决url地址的问题,以下是在读取文件的时候使用的代码:

vtpReader.setUrl("{{ url_for('static', filename='vtpModels/tttest.vtp') }}").then(beginRender)

3. 三维模型无法显示

如果成功读取了三维模型,但是页面上并没有显示出来,很有可能是您的css代码出现问题。可能是position: absolute等位置代码的问题,需要仔细检查并调整。

笔者同样出现过html中并未呈现三维模型的问题,后来才发现是css代码的问题,经过调整之后,问题得以解决。在上文的代码中,您可以参考本人的css设置,进而调整您自己的css代码。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Javascript VTK】在页面中放置VTK三维模型 的相关文章

随机推荐

  • add_library使用 $<TARGET_OBJECTS:name>

    一 背景 前面介绍了add library的两种格式 今天分享一个实例 Cmake分别生成静态链接库 OBJ链接库 并使用
  • 人人商城小程序消息服务器配置,人人商城小程序订阅消息设置方法和几个坑!...

    操作步骤 第一步 开通订阅消息功能 登录微信小程序官网后台 mp weixin qq com 开通订阅消息 第二步 服务类目 新增 商家自营 gt 服装 鞋 箱包 第三步 添加订阅消息 4个 订阅消息 公共模板库 搜索 订单支付成功通知 编
  • Android仿小米商城底部导航栏(基于BottomNavigationBar)

    简介 现在大多数App都会用到底部导航栏 比如QQ 微信和购物App等等 有了底部导航栏 用户可以随时切换界面 查看不同的内容 Android底部导航栏的实现方式特别多 例如TabHost TabLayout 或者TextView等 都可以
  • 机器学习-支持向量机算法实现与实例程序

    一 SMO算法基础 支持向量就是离分隔超平面最近的那些点 分隔超平面是将数据集分开来的决策边界 支持向量机将向量映射到一个更高维的空间里 在这个空间里建立有一个最大间隔超平面 在分开数据的超平面的两边建有两个互相平行的超平面 建立方向合适的
  • 剑指offer总结

    时间复杂度一般比空间复杂度更重要 因为改进时间对算法的要求更高 是空间换时间 还是时间换空间 一般要看具体的应用 对于普通的应用 一般是空间换时间 因为普通用户更关心速度 而且一般有足够的存储空间允许这样操作 对于嵌入式的软件 一般我们会用
  • 简说C++学习-第一章C++语言概述

    简说C 学习 第一章C 语言概述 1 C 语言的发展 1972年 贝尔实验室在B语言的基础上 做了进一步的充实和完善 设计出了C语言 C语言的优点 语言简洁 使用灵活 方便 具有丰富的运算符和数据类型 可以进行低级操作 适合开发系统软件 程
  • Java jdbc实现多表查询

    数据库中的一张表对应Java中的一个类 我这里示例的是学生类 老师类 成绩类 还有一个用于存储多表查询结果后的SelectAll类 public class Student 学生表 private Integer id 学生编号 priva
  • sublime text 3下载与安装详细教程

    一 下载 打开官网下载链接http www sublimetext com 3 下载Sublime Text 3 portable version 下载下来为 Sublime Text Build 3083 x64 zip 编辑器的包 解压
  • Linux 中统计指定目录下同一类文件总的大小

    root PC1 test ls a map a ped a txt b ped b txt root PC1 test ll h total 1 4G rw r r 1 root root 200M Dec 1 19 42 a map r
  • Tensorflow-2-Tensorboard使用

    一 概述 机器学习如此复杂 训练模型的时候 摸不清背后到底是如何运行的 自己设置的参数和关键变量 如果能看到在训练时的变化情况 可以为后面的参数调优阶段提供很大的便利 Tensorboard就是这样一个工具 它刻意将模型抽象成图像 tens
  • UmiJS基础+UmiUI安装使用+Mock使用示例+DvaJS案例

    title UmiJS基础 date 2022 09 12 19 20 27 tags React 框架 UmiJS categories 框架 UmiJS 介绍 官方文档 可扩展 Umi 实现了完整的生命周期 并使其插件化 Umi 内部功
  • 问题(02)Message 消息提示每次只弹出1个,不能同时出现2个

    项目场景 PC端开发 vue elementUI 问题描述 Message 消息提示同时出现2个 原因分析 Element UI的Message消息提示是点击一次触发一次的 解决方案 在utils文件创建resetMessage js re
  • Jar包中Class文件替换

    1 查找替换的class的具体路径 jar tvf jar grep class 根据自己的jar包和类名替换 2 根据第一步查到的class的具体路径解压出来对应文件 jar xvf jar class 3 替换解压出来的文件中的clas
  • 代码制作数字流星雨_用C语言编写流星雨程序

    展开全部 数字流星雨代码 流星雨 cpp Defines the entry point for the console application 程序名称 数字流星雨 最后修改e5a48de588b632313133353236313431
  • Python 判断生肖

    Python 判断年份干支纪年及生肖 生肖 12年一循环 干支纪年法 60年一循环 十天干 甲 乙 丙 丁 午 戊 庚 辛 壬 癸 十二地支 子 丑 寅 卯 辰 巳 午 未 申 酉 戌 亥 十二生肖 鼠 牛 虎 兔 龙 蛇 马 羊 猴 鸡
  • java 插件式架构_springboot插件式开发框架

    介绍 该框架主要是集成于springboot项目 用于开发插件式应用的集成框架 核心功能 插件配置式插拔于springboot项目 在springboot上可以进行插件式开发 扩展性极强 可以针对不同项目开发不同插件 进行不同插件jar包的
  • 【Cat.1模组】 广和通L610 基于OpenCPU的SDK二次开发

    目前支持Cat 1网络的芯片平台主要是紫光展锐UIS8910和翱捷ASR1603 基于紫光展锐平台 各大厂商延伸出多款Cat 1模组 广和通L610就是其中之一 本文记录开发过程 供日后参考 广和通L610模组支持AT指令开发和OpenCP
  • 2020安卓启动图标圆角_从零开始画图标系列:启动图标设计指南

    想要在启动图标设计上入门 就要先从规范开始学习 然后了解不同的风格以及对应风格的设计过程 说到启动图标的规范 首先会想到的 就是 iOS 提供的图标栅格 通过这个栅格 会规范图形的尺寸 以及所处的位置 这个模板和工具图标的使用方法类似 我们
  • JAVA代码实现多级树结构封装对象(2018-09-26补充)

    我们经常在代码里会造一个树结构对象 以方便前端使用 以地区 区 镇 村 为例 后台一般对于树结构对象在数据库的结构是这样的 主键ID 名字 父ID ID REGION NAME PARENT ID 121100 尼龙区 0 12110000
  • 【Javascript VTK】在页面中放置VTK三维模型

    一 问题描述 在项目的开发过程中 遇到将vtk三维重建结果放置到网页 Web Page 中进行可视化展示的棘手问题 想要实现的效果图如下 图一 最终实现的 v t k 3