CuraEngine工作流程及代码详解
FffProcessor类:线材热熔制造处理最初阶段,对网面进行切片,分层处理。
FffPolygonGenerator类:线材热熔制造的初期阶段,生成多边形。
FffGcodeWriter类:线材热熔制造处理第二阶段,已经生成的多边形轮廓将用于Gcode的生成。
libarcus库
libarcus库包含了c++和python的类(这里没用到python),用来创建一个socket。这个socket基于protobuf,用来发送和接收信息。就是在Cura(图形界面,前端)和CuraEngine(后端)传递信息。
protobuf库
Google Protocol Buffer(简称 Protobuf) 是Google公司内部的混合语言数据标准,用于结构化数据串行化,有如XML,不过它更小、更快、也更简单,可用于通讯协议、数据存储等领域。
目前提供了 C++、Java、Python 三种语言的 API。(因此可以将protobuf代码转换成c++代码)
commandSocket类
commandsocket类主要是为了与前端通信用的。
commandsocket.h中包含了utils/socket.h和utils/socket.cpp文件
只有CommandSocket类用到了Arcus库。
CommandSocket(fffProcessor* processor)新加数据处理器
void connect(const std::string& ip, int port)连接并且注册消息类型
void handleObjectList(Cura::ObjectList* list)添加数据
void handleSettingList(Cura::SettingList* list)为处理器配置参数
void sendPolygons(cura::PolygonType type, int layer_nr, cura::Polygons& polygons)发送某层的多边形信息。
void sendProgress(float amount)发送打印进度
void sendPrintTime()向接收端发送打印信息
void beginSendSlicedObject()发送开始切片
void endSendSlicedObject()发送结束切片
void beginGCode()生成gcode之前,设置gcodewriter输出流
void sendGCodeLayer()发送一层的gcode
void sendGCodePrefix(std::string prefix)发送gcode前缀代码
切片流程概述
从总体上讲,CuraEngine 的切片分为五个步骤:
步骤一:模型载入
计算机中的 3D 模型大多是以三角形面组合成的表面所包裹的空间来表示的。三角形作为 3D 模型的基本单元,有结构简单,通用性强,可组合成任意面的特点;空间坐标中只要三个点就可以表示一个唯一的三角形,两点只能表示一条直线,而再多的直线也无法组成一个平面;空间中的任意三个不共线的点都可以组成一个三角形,而四个点所组成的四边形就必需要求四点共面;任意的表面都可以拆解成三角形,一个四边形可以拆解成两个三角形,但一个三角形确没有办法用四边形组合而成。计算机所擅长的事情就是把简单的事情不断重复,而三角形正是因为这些特性,成为了计算机 3D 世界的基石。
CuraEngine 内部也是用三角形组合来表示模型的,不过同样一个三角形组合,确有无穷多种数据结构来进行存储,CuraEngine 切片的第一步,就是从外部读入模型数据,转换成以 CuraEngine 内部的数据结构所表示的三角形组合。
有了三角形组合还不够,CuraEngine 在载入模型阶段还要对三角形进行关联。两个三角形共有一条边的,就可以判断它们为相邻三角形,一个三角形有三条边,所以最多可以有三个相邻三角形,一般而言,如果模型是封闭的,那它的每一个三角形都会有三个相邻三角形。
有了三角形的相邻关系,就可以大幅提高下一个步骤分层过程的处理速度。Cura 之所以成为当前市场切片速度最快的软件,这是其中最显著的优化之一。
步骤二:分层
如果把模型放在 XY 平面上,Z 轴对应的就是模型高度。把 XY 平面抬高一定高度再与模型的表面相交,就可以得到模型在这个高度上层切片。所谓的分层就是每隔一定高度就用一个 XY 平面去和模型相交作层切片,全部切完后就可以得到模型在每一个高度上的轮廓线。就像是切土豆片一样,把一个圆的或不圆异或不管什么奇形怪状的土豆用菜刀一刀一刀切开,最后就能得到一盘薄如纸片的土豆片。
分层本质上就是一个把 3D 模型转化为一系列 2D 平面的过程,自此之后的所有操作就都是在 2D 图形的基础上进行了。
在前面模型载入阶段说到了 CuraEngine 埋了一个三角形关联的伏笔,现在知道,两个平面相交,得到的是一条直线,一个平面和一个三角形相交,就得到一条线段。当然也有可能什么也得不到,平台平行啦,三角形的三个点都在平面的同一面之类,这些可以不管,现在只关心和平面有交集的那些三角形即可。把一个平面和所有的三角形都相交了一遍,得到了许许多多的线段,但需要的是 2D 图形,三角形是 2D 图形,四边形,任意多边形都是 2D 图形,但线段不是。所以就要把这些线段试着连成一个多边形,那么问题来了,要把这些线段连起来,只能两个两个地去试,看看它们是不是共端点了,粗算一下,每一层都是平方级的复杂度,再算上层数,那就是三次方级了。但现在,我们知道了三角形的关联关系,两个关联的三角形,如果都与一个平面相交,那它们的交线一定也是关联的,这一下,每一条线段只需要判断三它与它相邻三角形,看看与这个平面有没有交线即可,一下子就把问题的复杂度降了一个次元。速度自然可以有质的提升。
步骤三:划分组件
经过分层之后,我们得到了一叠 2D 平面图形。接下来需要做的事情就是对每一层的平面图形进行划分,标记出哪里是外墻、内墙、填充、上下表面、支撑等等。
3D 打印在每一层是以组件单位,所谓组件指的就是每一层 2D 平面图形里可以连通的区域。而打印的顺序就每打印完一个组件,接着会挑选一个离上一个组件最近的组件作为下一个进行打印,如此循环直至一层的组件全部打印完成;接着会 Z 轴上升,重复上述步骤打印下一层的所有组件。
每一个组件的打印,先打边线再对边线内部填充。边线可以打印多层,最外层的边线称为外墙,其它的统称为内墙,CuraEngine 之所以要对内外墙进行区分,是为了可以为它们定制不同的打印参数:外墙会被人观察到,所以可以采用低速以提高表面质量,内墙只是起增加强度的作用,可以稍稍加快打印速度以节省时间。这些都可以在 Cura 界面的高级选项里进行配置。
内外墙标记完之后就是填充和上下表面的标记了,填充有一个填充率,0%填充率就是无填充,100%就是打成一个密实的平面,所以上下表面就是填充率为 100%的填充。中间的填充率自然介于两者之间,就像一张渔网,填充率越高网眼越细。
软件会先把内墙里面的部分统统标记成填充,之后再进一步判断其中有哪些部分要转换成为上下表面。至于是哪些部分,在设置里会有一个上下表面层数的设置,它代表了模型的上下与空气接触的表面有几层,这里就会用到这个参数,CuraEngine 会把当前层上下 n 层(上下表面层数)取出来与当前层进行比较,凡是当前层有而上下 n 层没有的部分就会被划归到表皮。而原来的填充区域在割除被划到表皮的部分后剩下的部分就是最终的填充区域。
CuraEngine 在处理过程中大量用到了 2D 图形运算操作,有关 2D 图形的运算,有很多人研究,也被做成许多成熟的库可以调用。CuraEngine 的作者拿来主义,选取了一个他认为比较好用的库,叫 ClipperLib 的库直接内嵌到软件之中,ClipperLib 所使用的 2D 图形算法也很著名,叫 Vatti’s clipping algorithm,很复杂。
CuraEngine 所用到的几种 2D 图形的运算,都是由 ClipperLib 实现的:交、并、差、偏移。
• 图形相交 二元图形操作,最终结果为两个图形共同包含的区域。记作:A * B
• 图形相并 二元图形操作,最终结果为两个图形其中的一个或两者所包含的区域。记作:A + B
• 图形相减 二元图形操作,最终结果为属于前者但不属于后者的区域。记作:A – B
• 图形偏移(外扩) 一元图形操作,最终结果为图形区域的边界向外扩展指定的距离。
• 图形偏移(内缩)
• 一元图形操作,最终结果为图形区域的边界向内收缩指定的距离。内缩与外扩互为逆运算。
这些就是 CuraEngine 所用到的 2D 图形操作,运算不多,确可以做许许多多的事情,比如上面所说的上下表面计算,就可以用数学公式来表示:
• 表面(i) = [填充(i) – 层(i + n)] + [填充(i) – 层(i – n)]
• 填充(i) = 填充(i) – 表面(i)
• 其中,i 为当前层号,n 为上下表面层数(可以不一样)。
• 同样的,组件里面内外墙,填充怎么划分,只用一个内缩运算就可以搞定:
• 外墙 = 组件.offset(-线宽)
• 内墙 1 = 组件.offset(-线宽 * 2)
• …
• 内墙 n = 组件.offset(-线宽 * (n + 1))
• 填充 = 组件.offset(-线宽 * (n + 2))
如果模型无需支撑,那组件划分到这里就可以收工。否则,接下就是计算支撑的时间了。
CuraEngine 最大的不足就是在支撑上,CuraEngine 所用的支撑算法:
CuraEngine 首先把整个打印空间在 XY 平台上划分成为 50um*50um 的网格,每个网格的中心点再延 Z 轴向上作一条直线,这条直线可能会与组成 3D 模型的三角形相交,三角形与直线的交点以及这个三角形的倾斜度会被记录到网格里面。
现在每个网格里会记录下一串被称为支撑点的列表,每个支撑点包含一个高度和一个倾斜度信息。接下来会对每个网格的支撑点列表按照高度从低到高排序。根据这些信息就可以判断模型上任意一个点是否需要支撑了,怎么判断:
从底面开始延着一条网格中心往上走,起始是在模型外部,当遇到第一个支撑点的时候,就从模型外部进行了模型内部,称这个支撑点为进点。
继续向上,遇到了第二个支撑点,从模型内部又退到了模型外部,称这个支撑点为出点。
接着向上,可以发现,进点与出点总是交替出现的。
利用这个规律,对于模型上任何一个点,只要找到这个点所对应的网格,再找到这个网格里在这个点以上最近的一个支撑点,就可以得到两个信息:这个点之上是否有模型悬空;这个点上面的悬空点的面的倾斜度是多少。
Cura 界面的专家设置里面有支撑角度的设置,如果一个点处于模型悬空部分以下,并且悬空点倾斜度大于支撑角度,那这个点就是需要支撑的。所一个平台上所有的需要支撑的点连接起来围成的 2D 图形就是支撑区域。
CuraEngine 所使用的支撑算法比较粗糙,但胜在速度很快。先不说网格化后失去了精度,通过倾斜角度来判断,模型下方一旦倾斜角发生了突变,像左图这种从负 45 度一下突变成正 45 度,倾斜角判断无能为力,除非把它改大到 60 度,这样的话,整个模型都会被过度支撑。这样矫枉过正,既不科学,也浪费材料和打印时间,还会对模型表面质量带来不好的影响。
科学的支撑算法应该是找到模型局部最低点进行支撑,最低点以上不一定需要支撑。因为 FDM 材料本身的粘性,使得材料的走线可以有一部分悬空而不坍塌,这个效果被称为 Overhang,只要上层材料的悬空距离小于一定的值,它就不需要支撑,这个距离应该在 1/4 到 1/2 线宽之间。
支撑范围确定之后,也和组件一样,可以有外墙、内墙、填充、表面。依样画葫芦即可。CuraEngine 对于支撑,只会生成外墙和填充。
组件和支撑就是 CuraEngine 在这一步所生成的