3D打印技术之切片引擎(5)

时间:2021-10-23 18:13:58

3D打印技术之切片引擎(5)

文章转自:http://blog.csdn.net/fourierfeng/article/details/47099289

从这一篇文章开始,就开始说填充。在3D打印切片技术中,填充算法是最核心的部分。

3D打印技术的常用的指标包括:打印速度,稳固性,柔韧性,逼真度,密度(关系着使用打印材料的多少),都与填充算法有着直接的关系。可以说衡量一个切片引擎的优劣主要看它的填充算法是否优越。

其实我开始研发切片引擎的时候,由于不是所有的模型打印都需要支撑,而且针对边界的分层技术又比较容易实现,所以前面的算法开发时间很短,大部分时间耗在了填充算法的研究上,也就是填充算法的不断优化使得我的引擎的打印质量不断的接近于skeinforge。

填充算法是一个世界难题,目前世界上最先进的填充算法也还是问题多多,如何最优化的填充使得模型兼顾稳固逼真,而且密度小,打印速度快。这不是简单的二维几何填充所能做的到的。我大学的专业是临床医学,记得人体解剖学中提到过的骨小梁结构,其实才是最合理的填充方式,但是如何用数学实现骨小梁,任重而道远啊。

行,马上进入正题,在这一篇文章里,我就先说一下填充算法中最简单的一种——线填充。
首先,需要引用计算机图形学二维图形的扫描线填充算法。如下图:

3D打印技术之切片引擎(5)

简单的描述:射线与多边形相交,与边界交点个数为奇数,为内点;偶数为外点或者边界点。但是,还有几种特殊情况需要特殊处理:每当一条扫描线经过多边形的一个顶点时,扫描线在该顶点处与多边形的两条边相交,这种情况可能导致在这条扫描线的交点列表上要增加两个点。当扫描线与奇数边相交时,这时候需要把扫描线与两侧的有公共交点的线的交点算作一个交点。如下图b线:

3D打印技术之切片引擎(5)

另外,还有边界线与扫描线重合的情况,需要完全规避掉。

对于最简单的线填充,掌握这个原理基本就没有问题了,所需要注意的就是上下两层不能同一方向的填充,避免打印的模型不稳定,可以考虑采用依次采用x轴平行的方向和y轴平行的方向。这样用一组等间距的平行线扫描,采用上述的算法,最简单的线填充就完成了。

但这样的线填充会引出一个问题,回顾一下在第一讲中说到的——一个成功的切片引擎应该满足的几点,这样的填充虽然看似基本满足要求,但绝大多数情况在这几点上做的很不够,尤其是某一层是个狭长的结构,每一行的扫描线就非常的短,打印机就走走停停,不断的更换打印方向,打印速度受到极大的影响,更重要的是扫描线对边界冲击次数太多,肯定要影响到表面的效果,影响美观,而且频繁的走走停停,更换方向,对机器的寿命也是极大的消耗。

可以考虑这样的解决方案:

  1. 第一步:把某一层的二维图形分割成若干个凸集或者近似凸集;
  2. 第二步,对每一个凸集进行主元分析。
  3. 第三步,利用得到的主元方向,先把x轴或者y轴变换到与主元平行的方向,这里假设该线性变换为 M ,利用上述的算法进行填充,然后再利用 M1 变换回原来的位置。

主元分析是利用协方差矩阵确定边界向量的主元向量,这里的点是二维,可简单表示:
μx=12ni=1n(beginPointxi+endPointxi)
其中 beginPointxi 为第 i 个边界矢量的起始向量的x分量, endPointxi 为第 i 个边界矢量的结束向量的x分量;y分量以此类推。下面就开始构造协方差矩阵 C C 的第 j 行第 k 列元素如下 (1j,k2) :
Ckj=12ni=1n(beginPointjibeginPointki+endPointjiendPointki)
其中: beginPoint1i=beginPointxiμx ,其他的以此类推。

最后根据数值计算求出协方差矩阵的三个特征向量,组成特征矩阵。特征矩阵即为我们要找的线性变换 M 。这是由于n维线性空间 V 到线性空间 V 的线性映射完全被它在 V 的一个基上的作用所决定。这里 V=V , 原空间的基可认定为:(1,0),(0,1)。则:
(1001)×M=(ε1ε2)
其中 ε1 ε2 为矩阵 C 的两个特征向量。乘号左边是单位矩阵,所以 M 即等于右边。下面给出主元分析的代码。

/// <summary> 
/// 向量减去平均向量
/// </summary>
/// <param name="v1">向量端点1</param>
/// <param name="v2">向量端点2</param>
/// <param name="pha">边界向量</param>
/// <param name="coor_sum">向量平均值</param>
void get_phasor_covariance( float3& v1, float3& v2, Phasor* pha, float3 coor_sum )
{
v1[0] = pha->beginPoint[0] - coor_sum[0];
v1[1] = pha->beginPoint[1] - coor_sum[1];
v2[0] = pha->endPoint[0] - coor_sum[0];
v2[1] = pha->endPoint[1] - coor_sum[1];
}


/// <summary>
/// 得到协方差矩阵元素
/// </summary>
/// <param name="pha">边界向量集合</param>
/// <param name="pha_num">边界向量数量</param>
/// <param name="coor_sum">平均向量</param>
/// <param name="j">矩阵行索引</param>
/// <param name="k">矩阵列索引</param>
float get_covariance_matrix_element( Phasor* pha, int pha_num, float3 coor_sum, int j, int k )
{
float element = 0;
float3 v1, v2;
for ( int i = 0; i != pha_num; ++i )
{
get_phasor_covariance( v1, v2, pha + i, coor_sum );
element += v1[j] * v1[k] + v2[j] * v2[k];
}

element /= pha_num * 2;
return element;
}

/// <summary>
/// 获得平均向量
/// </summary>
/// <param name="coor_sum">平均向量</param>
/// <param name="ps">边界环</param>
/// <param name="pha_num">边界环向量的数量</param>
void get_coor2_aver( float3& coor_sum, Phasor* ps, int pha_num )
{
int i;
memset(coor_sum,0,sizeof(float)*3);
for ( i = 0; i != pha_num; ++i )
{
coor_sum[0] += ps[i].beginPoint[0];
coor_sum[0] += ps[i].endPoint[0];
coor_sum[1] += ps[i].beginPoint[1];
coor_sum[1] += ps[i].endPoint[1];
}


coor_sum[0] /= pha_num * 2;
coor_sum[1] /= pha_num * 2;
}

/// <summary>
/// 从协方差矩阵得到特征矩阵
/// </summary>
/// <param name="a">协方差矩阵</param>
/// <param name="n">矩阵阶数</param>
/// <param name="v">特征矩阵</param>
/// <param name="eps">控制精度要求</param>
/// <param name="jt">控制最大迭代次数</param>
int eejcb( float a[], int n, float v[], float eps, int jt )
{
int i, j, p, q, u, w, t, s, l;
float fm, cn, sn, omega, x, y, d;
l = 1;
for ( i = 0; i <= n - 1; i++ )
{
v[i * n + i] = 1.0;
for ( j = 0; j <= n - 1; j++ )
{
if ( i != j )
{
v[i * n + j] = 0.0;
}
}
}
while ( 1 == 1 )
{
fm = 0.0;
for ( i = 0; i <= n - 1; i++ )
{
for ( j = 0; j <= n - 1; j++ )
{
d = fabs( a[i * n + j] );
if ( ( i != j ) && ( d > fm ) )
{
fm = d;
p = i;
q = j;
}
}
}
if ( fm < eps )
{
return( 1 );
}
if ( l > jt )
{
return( -1 );
}
l = l + 1;
u = p * n + q;
w = p * n + p;
t = q * n + p;
s = q * n + q;
x = -a[u];
y = ( a[s] - a[w] ) / 2.0;
omega = x / sqrt( x * x + y * y );
if ( y < 0.0 )
{
omega = -omega;
}
sn = 1.0 + sqrt( 1.0 - omega * omega );
sn = omega / sqrt( 2.0 * sn );
cn = sqrt( 1.0 - sn * sn );
fm = a[w];
a[w] = fm * cn * cn + a[s] * sn * sn + a[u] * omega;
a[s] = fm * sn * sn + a[s] * cn * cn - a[u] * omega;
a[u] = 0.0;
a[t] = 0.0;
for ( j = 0; j <= n - 1; j++ )
{
if ( ( j != p ) && ( j != q ) )
{
u = p * n + j;
w = q * n + j;
fm = a[u];
a[u] = fm * cn + a[w] * sn;
a[w] = -fm * sn + a[w] * cn;
}
}
for ( i = 0; i <= n - 1; i++ )
{
if ( ( i != p ) && ( i != q ) )
{
u = i * n + p;
w = i * n + q;
fm = a[u];
a[u] = fm * cn + a[w] * sn;
a[w] = -fm * sn + a[w] * cn;
}
}
for ( i = 0; i <= n - 1; i++ )
{
u = i * n + p;
w = i * n + q;
fm = v[u];
v[u] = fm * cn + v[w] * sn;
v[w] = -fm * sn + v[w] * cn;
}
}
return( 1 );
}


/// <summary>
/// 得到协方差矩阵
/// </summary>
/// <param name="cm">矩阵</param>
/// <param name="pha">边界向量集合</param>
/// <param name="pha_num">边界向量数量</param>
void get_covariance_matrix( float cm[], Phasor* pha, int pha_num )
{
float3 coor_sum;
get_coor2_aver( coor_sum, pha, pha_num );
cm[0] = get_covariance_matrix_element( pha, pha_num, coor_sum, 0, 0 );
cm[1] = get_covariance_matrix_element( pha, pha_num, coor_sum, 1, 0 );
cm[2] = get_covariance_matrix_element( pha, pha_num, coor_sum, 0, 1 );
cm[3] = get_covariance_matrix_element( pha, pha_num, coor_sum, 1, 1 );
}

/// <summary>
/// 从边界向量集合得到特征矩阵
/// </summary>
/// <param name="matrix_changeOfBase">特征矩阵</param>
/// <param name="pha">边界向量集合</param>
/// <param name="pha_num">边界向量数量</param>
int get_matrix_changeOfBase( float matrix_changeOfBase[], Phasor* pha, int pha_num )
{
float cm[4] =
{
0
};

get_covariance_matrix( cm, pha, pha_num );
return eejcb( cm, 2, matrix_changeOfBase, 0.0000001, 600 );
}

接近尾声要添加新的填充矢量把上边生成的填充矢量依次的首尾连接起来,要重新排序填充矢量,这样保证了打印机最小的断开次数和时间。这里要注意:选择最佳起始填充矢量,要把离得最近的填充矢量首尾相接;要保证全局最佳,而非局部最佳;避免扫描线频繁的跳转,最理想的情况是让扫描线“一笔画下来”。还要注意:扫描线太短机器疾走急停,如果扫描线太短,可以忽略掉。连接完毕的结果如下图(蓝绿相间的为填充线):

3D打印技术之切片引擎(5)

这篇文章就先到这,下面几篇文章将介绍另外几种填充算法。