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

一、问题描述

在项目的开发过程中,遇到将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代码。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
.05, false, false, false, 40), new Card(9, 6, 14, 0, 0.15, false, false, false, 55), new Card(7, 7, 15, 0, 0要在vtk.js绘制三维点,可以使用vtk.js提供的vtkPoints和vtkGlyph3D类。以下.2, false, false, false, 50), new Card(11, 4, 16, 0, 0是一个简单的例子: ```javascript // 创建一个vtkPoints对象 const points = vtk.Common.Core.vtkPoints.newInstance(); .1, false, false, false, 45) ); $cards2 = array( new Card(8, 7, // 添加点 points.insertNextPoint(0, 0, 0); points.insertNextPoint(1, 0, 014, 0, 0.1, false, false, false, 55), new Card(9, 5, ); points.insertNextPoint(0, 1, 0); points.insertNextPoint(0, 0, 1); // 创建15, 0, 0.2, false, false, false, 50), new Card(7, 8, 15, 0, 0.15, false, false, false, 60), new Card(11, 3, vtkGlyph3D对象 const glyph = vtk.Filters.General.vtkGlyph3D.newInstance({ input: points, }); // 设置glyph的16, 0, 0.05, false, false, false, 40), new Card(10, 4, 属性 glyph.setInputArrayToProcess(0, 0, 0, vtk.Common.DataModel.vtkDataObject.FIELD_ASSOCIATION_POINTS16, 0, 0.1, false, false, false, 45), new Card(12, 2, , 'Scalars'); glyph.setSourceConnection(vtk.Filters.Sources.vtkArrowSource.newInstance().getOutputPort()); glyph.setVectorModeToUse17, 0, 0.1, false, false, false, 35) ); // 创建队伍 $team1 =Normal(); // 创建vtkActor对象 const actor = vtk.Rendering.Core.vtkActor.newInstance(); actor.getProperty().setPointSize(5); // 设置 new Team($cards1); $team2 = new Team($cards2); // 开始战斗 $battle = new Battle($team1, $team2); $battle->start(); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值