一次在dita-ot的html5样式表中缝合3D场景的实践
“所以这个学自动化的家伙到底在做什么样的工作?似是敲代码又不完全是?如果不是程序员的话没有钱该怎么办?”有时候会以第三人称的视角这样看待自己。或许对抗职业发展焦虑的一个有效的办法就是静下心来做些事情吧。
于是这就开了一个新坑——试图把3D场景集成到dita-ot的html5样式表中去。这样用户就能与我们发布的电子手册(也就是一套网页)中的3D对象进行交互啦~虽然没法保证说加了这个什么3D场景就能显著地提升机器的销量,但是枯燥重复的日常工作中也要添点非功利性的,结合了能力与兴趣的调料嘛。
Table of Contents
0. 当我在上班干活时我在干什么?
因为许久没有更新,所以接下来有必要简单介绍下我的工作(助理技术文档工程师)所用到的技术,以及这些技术在创建可交付物中的关系。
我司生产的大型医疗设备(CT机、核磁共振机等)是相对复杂的系统。怎样定义这些大家伙的技术文档的架构呢?这就需要看看我们产品的特征:
- 产品由多个子系统构成。由于是加班巨多的制造业,与实物打交道会带来许多麻烦,因为技术文档需要体现每个硬件系统的技术参数信息。
- 产品推向市场需经药监局审核。这就需要满足各种国标企标IEC的法规,它们对技术文档也有规定,必须体现给定条件下的系统参数。
- 产品有系统化的部署工作流。在将大家伙运到客户现场之前要勘察场地,系统进场后要由服务工程师安装各个子系统。最后还要做性能测试。技术文档也要在这些过程中对硬件和软件操作予以指导。
- 以上三点是对于单个系统而言。可是事实是,野心蓬勃的公司做了多种模态(CT、MR、RT…)的产品,每种模态有多种型号,不同型号对应不同的子系统性能配置、以及软件包配置,并且它们常常会随着系统工程师和产品经理的神秘决策而变化。
- 部门人少、钱少、活儿多,交付压力大。
因此我进组之前的两年,同事们采用了“结构化”的写作思想(即dita),将研发过程中的出现的各种概念、数据、操作步骤编写到一条条各自独立且简练的“topic”中,再用“map”来组织topic之间的关系,形成章和小节的层次结构。为了应对硬件和软件配置频繁的变化,对每一个topic和map都实施相应的版本管理,以致于我们可以按照产品的实际需要,选择合适版本的topic和map,将其连缀成完整的技术文档。
1. 整活儿的起因
之前我们的技术文档是以 *.chm 帮助文件的形式,出现在医疗设备系统的控制台计算机的UI界面上的(也就是按下<F1>快捷键就蹦出这个帮助文件)。本文撰写时的大概三年前,领导说“现在‘H5’挺火的,我们也要搞‘H5’手册!”然而直到最近才找了供应商来开发html5的xslt样式表。
又有一次领导想让我给大家分享下怎样做flash动画,然而对于初中电脑课上三两天急就出来的“小鸡破壳”动画实在只有一个模糊的印象。并且也不想再研究早已被淘汰的技术。想到Three.js可以在网页中创建3D场景,那就自然而然地立下flag啦。
2. 技术概述和预期效果
2.1 技术概述
前面讲了方法论,现在说说在实践中我们都用到哪些软件,以及它们的作用。
- 方法论中提及的“topic”,“概念、数据、操作步骤”,使用开源的dita-ot工具包来定义。topic和map的本质是xml文件。
- 方法论中提及的topic和map的版本管理过程,由SDL Tridion Docs的内容管理平台来完成。Tridion Docs内部集成了dita-ot工具包。
- 同事们日常编写技术信息的过程就是编写topic和map的过程,由于直接编写xml代码容易出错,故使用Oxygenxml Author编辑器来实现可视化傻瓜式编辑。(就好比用frontpage做网页)
- 编写完成的xml文件需要经由dita-ot中的xslt样式表处理,可以生成多种格式的可交付物,如pdf文件(交给印刷厂印刷成书)、chm文件(部署在产品的UI上),以及目前计划在请外包供应商做的,“H5”网页(领导想放哪儿咱就放哪儿)。
2.2 预期效果
- 在经由xslt样式表输出的html5网页中可以显示3D场景,载入子系统部件的3D模型,用户可以与之交互。
- 对于日常编写xml文件的同事们来说,他们需要在xml文件中插入设置好的Javascript代码(用来定义3D场景的参数、控制模型的运动效果等),并提供3D模型文件。
- 至于我的这次实践呢,就是作为一枚缝合怪,将代码和3D模型缝合到html5网页手册中。这就需要魔改xslt样式表了。
3. 一些无意义的探索
工作以来,自从接触了部门用的这个叫做Tridion Docs(以下简称“TD”)的平台我就无比心累。安装目录下随处可见不明用途的配置文件,文件内容又是一河滩混乱且拥有晦涩含义的变量名和参数名。最糟糕的是它的社区比较贫瘠,除了官方文档和官方沉寂的论坛以外,几乎没有成规模的交流论坛和群组。因为懒得自己改html5样式表,于是寻思着从网上找个现成的装上去看看效果,哦豁,只有一个开源的dita-bootstrap的样式表吗?想象一下如果wordpress的开源主题只有一种,那它的生态将会是什么情形= =。
于是在爆出众多个fatal error后终于放弃了,究其原因是,dita-bootstrap-3.2(这已经是作者刻意为旧版dita-ot开发的legacy版样式表了,支持dita-ot版本为2.5。而我们陈旧的TD环境中集成的版本为2.3,并且两种版本的默认样式表元素差异还挺大。)
4. 开始修改!
4.1 修改转换网页内容的xslt样式表
既然第三方的样式表不中用,那就不搞花里胡哨的,直接切入正题!测试环境的html5样式表id为org.dita.UIH.html5(别问我UIH是什么意思),前往它的根目录下的入口文件plugin.xml,一路定位插件的调用关系,来到出厂默认的样式表org.dita.xhtml\xsl\xslhtml\dita2htmlImpl.xsl。html5样式表的正文部分就是在这里通过名为addContentToHtmlBodyElement的模板进行处理的。
于是可以在这个文件里魔改样式表,使其能够处理xml文件中特定的标签,获取标签中的信息,将其转化为html网页中的<canvas>标签。因为Three.js正是通过canvas技术得以在网页中显示3D内容的。
<!-- Add for "Support foreign content vocabularies such as MathML and SVG with <unknown> (#35) " in DITA 1.1 --> <!--lihao: add template on 20220410 --> <xsl:template match="*[contains(@class, ' topic/foreign ')]"> <div class="test">THIS IS TEST</div> <canvas id="mainCanvas" width="400px" height="300px"></canvas> <script type="text/javascript"> <xsl:value-of select="codeblock/text()" disable-output-escaping="yes" /> </script> </xsl:template>
添加的内容其实并不多,match后面的XPath语句选择了类型为topic的xml文件中的foreign标签。并在这样的标签中插入<canvas>画布来承载3D场景,同时也要插入一个<script>标签,存放用于定义和控制3D场景的Javascript代码。一个场景对应一套代码。而代码的内容则从<foreign>标签里的<codeblock>标签中提取得到。这里的disable-output-escaping=”yes”的功能是将value-of对应的值里的尖括号和”&”符号不作转换,原样输出。因为它们在xml文档和html文档中的含义不同,最终还是要顺应html的口味嘛。
4.2 修改xml文件
在Oxygenxml中编写的xml文件如上图所示。将Three.js创建场景的代码放置到<codeblock>标签中即可。至于创建场景的代码怎么编写呢,这就是个大坑啦,抛给同事们好了(偷笑)。不过估计他们要么让外部门同事帮忙做,要么就是经典的向大老板汇报:“我们需要寻求供应商资源!!”
需要注意到每一段代码都需要与xml中的<canvas>进行联动,我在xslt样式表中设置canvas的id为”mainCanvas”,因此这个id也要在Javascript的QuerySelector中体现。
4.3 引入Three.js库及其依赖
由于dita-ot转换得到的html文件是一组连同图片、css样式表等打包为zip的静态文件,并且我尚没法接触到客户端UI所处的后台服务,因此考虑使用非模块化的Three.js库文件。
那么应当把库文件放置在哪里呢?虽然样式表的参数有copycss这种参数,但是并没有类似于copyscript的方法(很不科学不是吗),或许这个问题可以留给专门负责定制样式的供应商来解决。然而有个替代性的方法可以引入Javascript代码:通过args.hdr这一项参数来指定一个xml文件,dita-ot会将文件中的内容插入到生成的html网页的header中。
(同时也有arg.ftr参数,但是我们需要先引入Three.js库,才能在网页的正文中顺利地执行前面编写的Javascript代码。因此选择args.hdr。)
编写这个javascript.hdr.xml文件的方法是:
<div> <script type="text/javascript"> <![CDATA[ /* Three.min.js */ /* OrbitControls.js */ /* chevrotain.min.js */ /* VRMLLoader.js */ ]] </script> </div>
获取到这些js库文件后,将其内容粘贴到所示的部分。需要注意Three.min.js在最前,OrbitControls和VRMLLoader是其插件,并且chevrotain又是VRMLLoader所必需的。这样的操作相对于模块化管理,当然是有些麻烦。
注意:Three.js的插件(例如OrbitControls.js)需要从官方release文件的examples\js\目录下获得。
再注意:CDATA也是为了使得Three.js库及其插件的代码中的尖括号和”&”符号被正确地插入到html文件的头部。因此必须要加上。
5. 发布
修改好xslt样式表,随便绘制了一个圈圈,再加个光源和贴图,效果如下~~鼠标左键拖动可以旋转,右键拖动可以平移,滚轮可以缩放。当然了,这里只是一张静态的截图啦~
我们公司负责设计部件结构(如机架、模体、线圈组件等)的外部门同事们使用的软件是solidworks。而Solidworks导出的VRML文件可以被加载到Three.js的场景中,所以这也是为什么要安装VRMLLoader加载器插件的原因。
在后续的文章中,我将进一步摸索3D模型在TD内容库中的管理方式,以及如何与Oxygenxml进行耦合,才能使得同事们可以自信地将其他同事设计出的高大上的结构件(而不是我画的面包圈)安置于场景中。为用户展示逼真的产品模型,用更直观的方式传递帮助信息,才是这一系列采坑的最终目的。
也祝愿自己不会润而鸽之。