将外部支持者生成网格以进行3D打印

时间:2018-03-29 06:59:06

标签: c++ 3d geometry computational-geometry 3d-printing

序言

这是我试图重新询问已关闭的Generating supporters for 3D printing,因为这是一个有趣的问题,但缺乏重要的细节...这是 Q& A ,目前我正在努力答案的代码,但随时回答(我接受最佳答案)。

问题描述

好的,这里有一些关于这个问题的基本信息:

由于这是一个很大的问题,我将重点关注通用网格/支持模式合并几何问题。

简而言之如果我们想要打印任何网格,只有连接到起始平面直到~45度(+/-,适用于不同的打印技术),我们才能这样做。因此,如果我们得到的部件没有连接到这个平面,我们需要创建一个桥接器来保持/连接它。这样的事情(从上面链接的页面拍摄的图像):

overview

粗糙的我们需要添加尽可能少量的材料,并且仍然具有足够的强度以将我们的网格保持在适当的位置而不会弯曲。最重要的是,我们需要削弱网格附近的支撑,以便在打印后轻松折断。

不要忘记,形状和位置取决于许多因素,如材料和技术,热流。

问题:

为了将这个巨大的主题缩小到可回答的问题,让我们只关注这个问题:

如何将 3D 三角网格(像STL这样的边界表示)与预定义的支撑模式(如3侧棱镜)合并,将其从垂直定义的平面连接起来?

使用简单的 C ++

1 个答案:

答案 0 :(得分:2)

好的,让我们从绝对的基础知识开始。

  1. 支持形状

    您可以使用任何形状来满足使用过的打印技术的细节。在 STL 中最容易生成的是3个侧棱镜,其形状包含2个三角形底边(顶部和底部)和3个边,所有边都有2个三角形。总共有8个三角形。

    support pattern shape

    此形状将从某个基准平面(Z=0)开始,并且会一直向上直到它撞击网格。但是为了完成这项工作,支持必须在网格和自身之间有一个小gap,我们将在后面添加网格的弱化关节结构。

  2. 支持模式

    这里有很多选项,所以我选择了最简单的(但不是防污),那就是将支撑放在支撑间距离为grid的均匀网格中。

    如此简单地从基准平面上的每个网格位置向上投射光线并检查与网格的交点。如果发现支撑位于交叉点下方gap高度的位置。

  3. <强>接头

    这个想法是将非常薄的支撑的扇子连接成锥形连接和放大器。以小于45度的角度覆盖主支撑棱镜上方的支撑表面(因此gap应足够大,以此方式覆盖grid距离。

    这里的主要问题是我们必须细分我们连接的三角形,以便我们满足 STL 网格属性。为了解决连接问题(避免漏洞或破坏 STL 的连接要求),我们可以使用不同的实体用于支撑,并为网格使用不同的实体。这也将使我们能够触摸表面而无需对它们进行重新三角化,从而使这项工作变得更加容易。

    为简单起见,我选择了四面体形状,这种形状很容易用三角形构造,并且在网格/支撑关节处也存在弱点。

  4. 因此,让我们进行一些测试 STL 网格并将其放在我们的基准平面之上:

    placement

    并放置我们的主要支持:

    main supports

    以及关节:

    joints

    joint base detail

    STL3D.h的VCL / C ++代码:

    //---------------------------------------------------------------------------
    //--- simple STL 3D mesh ----------------------------------------------------
    //---------------------------------------------------------------------------
    #ifndef _STL3D_h
    #define _STL3D_h
    //---------------------------------------------------------------------------
    #ifdef ComctrlsHPP
    TProgressBar *progress=NULL;        // loading progress bar for realy big STL files
    #endif
    void _progress_init(int n);
    void _progress     (int ix);
    void _progress_done();
    //---------------------------------------------------------------------------
    class STL3D                         // STL 3D mesh
        {                                                                      
    public:
        double center[3],size[3],rmax;  // bbox center,half sizes, max(size[])
        struct _fac
            {
            float p[3][3];              // triangle vertexes CCW order
            float n[3];                 // triangle unit normal pointing out
            WORD attr;
            _fac()  {}
            _fac(_fac& a)   { *this=a; }
            ~_fac() {}
            _fac* operator = (const _fac *a) { *this=*a; return this; }
            //_fac* operator = (const _fac &a) { ...copy... return this; }
            void compute()                                  // compute normal
                {
                float a[3],b[3];
                vectorf_sub(a,p[1],p[0]);
                vectorf_sub(b,p[2],p[1]);
                vectorf_mul(n,a,b);
                vectorf_one(n,n);
                }
            double intersect_ray(double *pos,double *dir)   // return -1 or distance to triangle and unit ray intersection
                {
                double p0[3],p1[3],p2[3];                   // input triangle vertexes
                double e1[3],e2[3],pp[3],qq[3],rr[3];       // dir must be unit vector !!!
                double t,u,v,det,idet;
                // get points
                vector_ld(p0,p[0][0],p[0][1],p[0][2]);
                vector_ld(p1,p[1][0],p[1][1],p[1][2]);
                vector_ld(p2,p[2][0],p[2][1],p[2][2]);
                //compute ray triangle intersection
                vector_sub(e1,p1,p0);
                vector_sub(e2,p2,p0);
                // Calculate planes normal vector
                vector_mul(pp,dir,e2);
                det=vector_mul(e1,pp);
                // Ray is parallel to plane
                if (fabs(det)<1e-8) return -1.0;
                idet=1.0/det;
                vector_sub(rr,pos,p0);
                u=vector_mul(rr,pp)*idet;
                if ((u<0.0)||(u>1.0)) return -1.0;
                vector_mul(qq,rr,e1);
                v=vector_mul(dir,qq)*idet;
                if ((v<0.0)||(u+v>1.0)) return -1.0;
                // distance
                t=vector_mul(e2,qq)*idet;
                if (t<0.0) t=-1.0;
                return t;
                }
            };
        List<_fac> fac;                         // faces
    
        STL3D() { reset(); }
        STL3D(STL3D& a) { *this=a; }
        ~STL3D() {}
        STL3D* operator = (const STL3D *a) { *this=*a; return this; }
        //STL3D* operator = (const STL3D &a) { ...copy... return this; }
    
        void reset(){ fac.num=0; compute(); }   // clear STL
        void draw();                            // render STL mesh (OpenGL)
        void draw_normals(float size);          // render STL normals (OpenGL)
        void compute();                         // compute bbox
        void compute_normals();                 // recompute normals from points
        void supports(reper &obj);              // compute supports with obj placement above base plane z=0
        void load(AnsiString name);
        void save(AnsiString name);
        };
    //---------------------------------------------------------------------------
    void STL3D::draw()
        {
        _fac *f; int i,j; BYTE r,g,b;
        glBegin(GL_TRIANGLES);
        for (f=fac.dat,i=0;i<fac.num;i++,f++)
            {
            glNormal3fv(f->n);
            if (f->attr<32768)
                {
                r= f->attr     &31; r<<=3;
                g=(f->attr>> 5)&31; g<<=3;
                b=(f->attr>>10)&31; b<<=3;
                glColor3ub(r,g,b);
                }
            for (j=0;j<3;j++) glVertex3fv(f->p[j]);
            }
        glEnd();
        }
    //---------------------------------------------------------------------------
    void STL3D::draw_normals(float size)
        {
        _fac *f;
        int i; float a[3],b[3];
        glBegin(GL_LINES);
        for (f=fac.dat,i=0;i<fac.num;i++,f++)
            {
            vectorf_add(a,f->p[0],f->p[1]);
            vectorf_add(a,a      ,f->p[2]);
            vectorf_mul(a,a,1.0/3.0);
            vectorf_mul(b,f->n,size); glVertex3fv(a);
            vectorf_add(b,b,a);       glVertex3fv(b);
            }
        glEnd();
        }
    //---------------------------------------------------------------------------
    void STL3D::compute()
        {
        _fac *f;
        int i,j,k;
        double p0[3],p1[3];
        vector_ld(center,0.0,0.0,0.0);
        vector_ld(size,0.0,0.0,0.0);
        rmax=0.0;
        if (fac.num==0) return;
        // bbox
        for (k=0;k<3;k++) p0[k]=fac.dat[0].p[0][k];
        for (k=0;k<3;k++) p1[k]=fac.dat[0].p[0][k];
        for (f=fac.dat,i=0;i<fac.num;i++,f++)
         for (j=0;j<3;j++)
          for (k=0;k<3;k++)
            {
            if (p0[k]>f->p[j][k]) p0[k]=f->p[j][k];
            if (p1[k]<f->p[j][k]) p1[k]=f->p[j][k];
            }
        vector_add(center,p0,p1); vector_mul(center,center,0.5);
        vector_sub(size  ,p1,p0); vector_mul(size  ,size  ,0.5);
                          rmax=size[0];
        if (rmax<size[1]) rmax=size[1];
        if (rmax<size[2]) rmax=size[2];
        // attr repair
        for (f=fac.dat,i=0;i<fac.num;i++,f++)
         if (f->attr==0) f->attr=32768;
        }
    //---------------------------------------------------------------------------
    void STL3D::compute_normals()
        {
        _fac *f; int i;
        for (f=fac.dat,i=0;i<fac.num;i++,f++) f->compute();
        }
    //---------------------------------------------------------------------------
    void STL3D::supports(reper &obj)
        {
        _fac *f,ff;
        int i,j,k;
        double p[3],dp[3],x0,y0,h0,x1,y1,x2,y2,h1,t;
        // some config values first
        const WORD   attr0=31<<10;              // support attr should be different than joint
        const WORD   attr1=31<<5;               // joint attr should be different than mesh,support
        const double grid0=8.0;                 // distance between supports
        const double grid1=2.0;                 // distance between joints
        const double gap=grid0/tan(45.0*deg);// distance between main support and mesh (joint size)
        const double ha=1.0;                    // main support side size
        // do not mess with these
        const double hx=    ha*cos(60.0*deg);   // half size of main support in x
        const double hy=0.5*ha*sin(60.0*deg);   // half size of main support in y
        const double grid2=0.4*hy;              // distance between joints bases
        const double ga=2.0*grid2*grid1/grid0;  // main support side size
        const double gx=hx*grid2/grid0;         // half size of joint support in x
        const double gy=hy*grid2/grid0;         // half size of joint support in y
    
        // apply placement obj (may lose some accuracy) not needed if matrices are not used
        for (f=fac.dat,i=0;i<fac.num;i++,f++)
            {
            for (j=0;j<3;j++)
                {
                for (k=0;k<3;k++) p[k]=f->p[j][k];  // float->double
                obj.l2g(p,p);
                for (k=0;k<3;k++) f->p[j][k]=p[k];  // double->float
                }
            for (k=0;k<3;k++) p[k]=f->n[k]; // float->double
            obj.l2g_dir(p,p);
            for (k=0;k<3;k++) f->n[k]=p[k]; // double->float
            } compute();
    
        // create supports
        for (x0=center[0]-size[0]+(0.5*grid0);x0<=center[0]+size[0]-(0.5*grid0);x0+=grid0)
         for (y0=center[1]-size[1]+(0.5*grid0);y0<=center[1]+size[1]-(0.5*grid0);y0+=grid0)
            {
            // cast ray x0,y0,0 in Z+ direction to check for mesh intersection to compute the support height h0
            h0=center[2]+size[2]+1e6;
            vector_ld(p,x0,y0,0.0);
            vector_ld(dp,0.0,0.0,+1.0);
            for (f=fac.dat,i=0;i<fac.num;i++,f++)
                {
                t=f->intersect_ray(p,dp);
                if ((t>=0.0)&&(t<h0)) h0=t;
                }
            if (h0>center[2]+size[2]+1e5) continue; // skip non intersected rays
            h0-=gap; if (h0<0.0) h0=0.0;
            // main suport prism
            ff.attr=attr0;
            // sides
            ff.attr=attr0;
            vectorf_ld(ff.p[0],x0-hx,y0-hy,0.0);
            vectorf_ld(ff.p[1],x0+hx,y0-hy,0.0);
            vectorf_ld(ff.p[2],x0-hx,y0-hy, h0); ff.compute(); fac.add(ff);
            vectorf_ld(ff.p[0],x0+hx,y0-hy,0.0);
            vectorf_ld(ff.p[1],x0+hx,y0-hy, h0);
            vectorf_ld(ff.p[2],x0-hx,y0-hy, h0); ff.compute(); fac.add(ff);
    
            vectorf_ld(ff.p[0],x0-hx,y0-hy, h0);
            vectorf_ld(ff.p[1],x0   ,y0+hy,0.0);
            vectorf_ld(ff.p[2],x0-hx,y0-hy,0.0); ff.compute(); fac.add(ff);
            vectorf_ld(ff.p[0],x0-hx,y0-hy, h0);
            vectorf_ld(ff.p[1],x0   ,y0+hy, h0);
            vectorf_ld(ff.p[2],x0   ,y0+hy,0.0); ff.compute(); fac.add(ff);
    
            vectorf_ld(ff.p[0],x0   ,y0+hy, h0);
            vectorf_ld(ff.p[1],x0+hx,y0-hy,0.0);
            vectorf_ld(ff.p[2],x0   ,y0+hy,0.0); ff.compute(); fac.add(ff);
            vectorf_ld(ff.p[0],x0   ,y0+hy, h0);
            vectorf_ld(ff.p[1],x0+hx,y0-hy, h0);
            vectorf_ld(ff.p[2],x0+hx,y0-hy,0.0); ff.compute(); fac.add(ff);
            // base triangles
            vectorf_ld(ff.p[0],x0   ,y0+hy,0.0);
            vectorf_ld(ff.p[1],x0+hx,y0-hy,0.0);
            vectorf_ld(ff.p[2],x0-hx,y0-hy,0.0); ff.compute(); fac.add(ff);
            vectorf_ld(ff.p[0],x0-hx,y0-hy, h0);
            vectorf_ld(ff.p[1],x0+hx,y0-hy, h0);
            vectorf_ld(ff.p[2],x0   ,y0+hy, h0); ff.compute(); fac.add(ff);
    
            // joints
            for (x1=x0-(0.5*grid0),x2=x0-(0.5*grid2);x1<=x0+(0.5*grid0);x1+=grid1,x2+=ga)
             for (y1=y0-(0.5*grid0),y2=y0-(1.9*grid2);y1<=y0+(0.5*grid0);y1+=grid1,y2+=ga)
                {
                // cast ray x1,y1,0 in Z+ direction to check for mesh intersection to compute the joint height h1
                h1=h0+gap+1e6;
                vector_ld(p,x1,y1,0.0);
                vector_ld(dp,0.0,0.0,+1.0);
                for (f=fac.dat,i=0;i<fac.num;i++,f++)
                    {
                    t=f->intersect_ray(p,dp);
                    if ((t>=0.0)&&(t<h1)) h1=t;
                    }
                if (h1>h0+gap+1e5) continue; // skip non intersected rays
                // tetrahedron joints
                ff.attr=attr1;
                // base triangle
                vectorf_ld(ff.p[0],x2   ,y2+gy,h0);
                vectorf_ld(ff.p[1],x2+gx,y2-gy,h0);
                vectorf_ld(ff.p[2],x2-gx,y2-gy,h0); ff.compute(); fac.add(ff);
                // sides
                vectorf_ld(ff.p[0],x2+gx,y2-gy,h0);
                vectorf_ld(ff.p[1],x2   ,y2+gy,h0);
                vectorf_ld(ff.p[2],x1   ,y1   ,h1); ff.compute(); fac.add(ff);
                vectorf_ld(ff.p[0],x2   ,y2+gy,h0);
                vectorf_ld(ff.p[1],x2-gx,y2-gy,h0);
                vectorf_ld(ff.p[2],x1   ,y1   ,h1); ff.compute(); fac.add(ff);
                vectorf_ld(ff.p[0],x2+gx,y2+gy,h0);
                vectorf_ld(ff.p[1],x2-gx,y2-gy,h0);
                vectorf_ld(ff.p[2],x1   ,y1   ,h1); ff.compute(); fac.add(ff);
                }
            }
    
        // reverse placement obj (may lose some accuracy) not needed if matrices are not used
        for (f=fac.dat,i=0;i<fac.num;i++,f++)
            {
            for (j=0;j<3;j++)
                {
                for (k=0;k<3;k++) p[k]=f->p[j][k];  // float->double
                obj.g2l(p,p);
                for (k=0;k<3;k++) f->p[j][k]=p[k];  // double->float
                }
            for (k=0;k<3;k++) p[k]=f->n[k]; // float->double
            obj.g2l_dir(p,p);
            for (k=0;k<3;k++) f->n[k]=p[k]; // double->float
            } compute();
        }
    //---------------------------------------------------------------------------
    void STL3D::load(AnsiString name)
        {
        int   adr,siz,hnd;
        BYTE *dat;
        AnsiString lin,s;
        int i,j,l,n;
        _fac f;
    
        reset(); f.attr=0;
        siz=0;
        hnd=FileOpen(name,fmOpenRead);
        if (hnd<0) return;
        siz=FileSeek(hnd,0,2);
            FileSeek(hnd,0,0);
        dat=new BYTE[siz];
        if (dat==NULL) { FileClose(hnd); return; }
        FileRead(hnd,dat,siz);
        FileClose(hnd);
    
        adr=0; s=txt_load_str(dat,siz,adr,true);
        // ASCII
        if (s=="solid")
            {
            _progress_init(siz); int progress_cnt=0;
            for (adr=0;adr<siz;)
                {
                progress_cnt++; if (progress_cnt>=128) { progress_cnt=0; _progress(adr); }
                lin=txt_load_lin(dat,siz,adr,true);
                for (i=1,l=lin.Length();i<=l;)
                    {
                    s=str_load_str(lin,i,true);
                    if (s=="solid") { name=str_load_str(lin,i,true); break; }
                    if (s=="endsolid") break;
                    if (s=="facet")
                        {
                        j=0;
                        s=str_load_str(lin,i,true);
                        f.n[0]=str2num(str_load_str(lin,i,true));
                        f.n[1]=str2num(str_load_str(lin,i,true));
                        f.n[2]=str2num(str_load_str(lin,i,true));
                        }
                    if (s=="vertex")
                     if (j<3)
                        {
                        f.p[j][0]=str2num(str_load_str(lin,i,true));
                        f.p[j][1]=str2num(str_load_str(lin,i,true));
                        f.p[j][2]=str2num(str_load_str(lin,i,true));
                        j++;
                        if (j==3) fac.add(f);
                        }
                    break;
                    }
                }
            }
        // binary
        else{
            adr=80;
            n=((DWORD*)(dat+adr))[0]; adr+=4;
            fac.allocate(n); fac.num=0;
            _progress_init(n); int progress_cnt=0;
            for (i=0;i<n;i++)
                {
                if (adr+50>siz) break;  // error
                progress_cnt++; if (progress_cnt>=128) { progress_cnt=0; _progress(i); }
                f.n[0]=((float*)(dat+adr))[0]; adr+=4;
                f.n[1]=((float*)(dat+adr))[0]; adr+=4;
                f.n[2]=((float*)(dat+adr))[0]; adr+=4;
                for (j=0;j<3;j++)
                    {
                    f.p[j][0]=((float*)(dat+adr))[0]; adr+=4;
                    f.p[j][1]=((float*)(dat+adr))[0]; adr+=4;
                    f.p[j][2]=((float*)(dat+adr))[0]; adr+=4;
                    }
                f.attr=((WORD*)(dat+adr))[0]; adr+=2;   // attributes
                fac.add(f);
                }
            }
        _progress_done();
        delete[] dat;
        compute();
        }
    //---------------------------------------------------------------------------
    void STL3D::save(AnsiString name)
        {
        // ToDo
        }
    //---------------------------------------------------------------------------
    void _progress_init(int n)
        {
        #ifdef ComctrlsHPP
        if (progress==NULL) return;
        progress->Position=0;
        progress->Max=n;
        progress->Visible=true;
        #endif
        }
    //---------------------------------------------------------------------------
    void _progress     (int ix)
        {
        #ifdef ComctrlsHPP
        if (progress==NULL) return;
        progress->Position=ix;
        progress->Update();
        #endif
        }
    //---------------------------------------------------------------------------
    void _progress_done()
        {
        #ifdef ComctrlsHPP
        if (progress==NULL) return;
        progress->Visible=false;
        #endif
        }
    //---------------------------------------------------------------------------
    #endif
    //---------------------------------------------------------------------------
    

    用法很简单:

    #include "STL3D.h"                  // STL mesh (this is the important stuff)
    STL3D mesh;                         // point cloud and tetrahedronal mesh
    
    mesh.load("space_invader_magnet.stl");
    mesh.supports(obj); //  obj is object holding 4x4 uniform matrix of placement if you STL is already placed than it is not needed
    

    我使用了很多来自我的OpenGL引擎的东西,比如动态List<>模板:


    List<double> xxx;double xxx[];相同
    xxx.add(5);5添加到列表的末尾
    xxx[7]访问数组元素(安全)
    xxx.dat[7]访问数组元素(不安全但快速直接访问)
    xxx.num是数组的实际使用大小
    xxx.reset()清除数组并设置xxx.num=0
    xxx.allocate(100)100项目预分配空间

    或向量和矩阵数学(vectorf_float*vector_double一起使用)并不太重要。如果你需要数学,请参阅:

    如果 STL 已经放置(没有矩阵)而不是放置转换,则根本不需要obj。代码反映了上面的子弹。我想保持尽可能简单,所以还没有优化。

    gapgrid常量在支持函数中是硬编码的,尚未设置为有效值。

    <强> [注释]

    现在这几乎没有涵盖问题的基本问题,并且有很多边缘情况未经处理以保持“短”。代码本身不检查三角形是否在45度斜率之上,但可以通过简单的正常角度检查来完成,如:

    if (acos(dot(normal,(0.0,0.0,1.0))<45.0*deg) continue;
    

    还需要在网格的各个部分之间添加支撑,例如,如果您的对象具有更多层,则仅从基础平面支持第一层。其余的必须使用下面的层......并在支撑的两侧使用弱化的接头。这类似于放置你需要在两个方向上投射光线的第一层支撑......或者投射连续光线穿过整个bbox并通过分析光线的法线方向来检查开始/结束表面(点的简单符号)产品)。例如,这是可能需要它的网格放置(对于某些技术):

    star

    在设计支撑时,请记住,您应该满足打印过程的正确缠绕规则(CCW)和正常方向(输出)...