3D data set to stereo lithography input

Hello,

I need to get data describing a 3D surface (from AFM data... it's just z values sampled over a uniform xy grid) to a stereo lithography machine. Has anyone tackled this before?

Apparently, an stl file is a standard input file for these devices. This file describes the surface via a mesh of triangles along with the normal to each triangle. The file then consists of an xyz triplet for each vertex of each triangle along with the normal. I haven't found anything (yet) in Igor to create the mesh. Image transform seems to be the likely suspect, but this hasn't panned out, unless I'm missing something.

I'm hopeful this post will generate some ideas.

Thanks,

Jeff
I suggest looking at the ImageInterpolate operation with the Voronoi method. This requires an x,y,z triplet source wave, so you will have to convert your gridded data to a big triplet wave. Then use the /STW flag to save the Delauney trangulation data.
Hello Jeff,

Indeed ImageTransform has a well-hidden feature called ccsubdivision. However, the Catmull-Clark subdivision is probably more appropriate for Pixar-type applications then for what you are doing. It is designed to create a higher density mesh so you can describe smoother surfaces.

If your original data represent samples on a regular rectangular grid then the actual triangulation is trivial, though non-unique as for each rectangle there are two choices of triangle representation. IP7 Gizmo does this calculation internally, i.e., for each rectangle create two triangles and compute the normals. The calculation of the normals is straightforward: pick a vertex of the triangle and compute the cross-product of the two vectors from the selected vertex to the other two triangle vertices.

Feel free to contact me directly through support@wavemetrics.com and I'll help you with that.

A.G.
WaveMetrics, Inc.
Stephen -- Thanks for your reply. I've looked into it, but I'm not sure if it's providing the info that I need. Maybe AG will comment.

AG -- I will follow up privately and post my results later.

Thanks,

Jeff
jtigor wrote:
Stephen -- Thanks for your reply. I've looked into it, but I'm not sure if it's providing the info that I need. Maybe AG will comment.
Jeff


I thought it was obvious that Steve is correct and that you would get the triangulation for your data using ImageInterpolate Voronoi.

The down-side of this approach is that you are taking data that are essentially already triangulated and you run a very complex calculation, O(N^2), just to obtain a triangulation which you already know. Also, regardless of how you obtain the triangulation, you still need to generate the normals in a consistent manner. The latter, in my view, is the more interesting part of the problem.

AG
Igor wrote:
I thought it was obvious that Steve is correct and that you would get the triangulation for your data using ImageInterpolate Voronoi.
...
Also, regardless of how you obtain the triangulation, you still need to generate the normals in a consistent manner. The latter, in my view, is the more interesting part of the problem.
AG


The output of ImageInterpolate for Voronoi with the /STW flag was a single column wave (W_TriangulationData). Are the components of each vertex on sequential rows in this wave?

At this point, it's all interesting for me.

Thanks,

Jeff
jtigor wrote:

The output of ImageInterpolate for Voronoi with the /STW flag was a single column wave (W_TriangulationData). Are the components of each vertex on sequential rows in this wave?
Jeff


If you just want the "edges" you can use the /SV flag. The /STW flag results in a wave that is designed to be used internally; it is intentionally undocumented as it contains information that is used to reconstruct the internal structures that are built during the triangulation process; it does not have direct vertex information.

Below I have pasted complete code for generating the triangles and their normals for a 2D matrix wave. This is not necessarily a very efficient way of computing this but it should get the job done.

I hope this helps,

AG

Function makeMeTrianglesAndNormals(inWave)
    Wave inWave
   
    Variable rows=DimSize(inWave,0)
    Variable cols=DimSize(inWave,1)
    Variable numTriangles=2*(rows-1)*(cols-1)
    if(numTriangles<=0)
        doAlert 0, "What am I doing here"
        return 0
    endif
   
    Make/O/N=(numTriangles*3,3) triangleWave
   
    Variable triangleLineIndex=0
    Variable xx,yy,zz,i,j
    for(i=1;i<rows;i+=1)
        for(j=1;j<cols;j+=1)
            getTopTriangle(inWave,i,j,triangleWave,triangleLineIndex)
            triangleLineIndex+=3
            getBottomTriangle(inWave,i,j,triangleWave,triangleLineIndex)
            triangleLineIndex+=3
        endfor
    endfor
   
    Make/O/N=(numTriangles,3) normalsWave
    Make/FREE/D/N=(3) nv
    Variable d,x1,y1,z1,x2,y2,z2
    triangleLineIndex=0
    for(i=0;i<numTriangles;i+=1)
        x1=triangleWave[triangleLineIndex+1][0]-triangleWave[triangleLineIndex][0]
        y1=triangleWave[triangleLineIndex+1][1]-triangleWave[triangleLineIndex][1]
        z1=triangleWave[triangleLineIndex+1][2]-triangleWave[triangleLineIndex][2]
        x2=triangleWave[triangleLineIndex][0]-triangleWave[triangleLineIndex+2][0]
        y2=triangleWave[triangleLineIndex][1]-triangleWave[triangleLineIndex+2][1]
        z2=triangleWave[triangleLineIndex][2]-triangleWave[triangleLineIndex+2][2]
        triangleLineIndex+=3
        nv[0]=y1*z2-z1*y2
        nv[1]=z1*x2-x1*z2
        nv[2]=x1*y2-y1*x2
        d=norm(nv)
        if(d<=0)
            nv[2]=1     // should not happen
        else
            nv[0]/=d
            nv[1]/=d
            nv[2]/=d
        endif
        normalsWave[i][]=nv[q]
    endfor
End


Function getTopTriangle(inWave,i,j,triangleWave,triangleLineIndex)
    Wave inWave,triangleWave
    Variable i,j,triangleLineIndex

    // the upper triangle consists of (i,j-1),(i-1,j),(i-1,j-1)
    triangleWave[triangleLineIndex][0]=DimOffset(inWave,0)+i*DimDelta(inWave,0)
    triangleWave[triangleLineIndex][1]=DimOffset(inWave,1)+(j-1)*DimDelta(inWave,1)
    triangleWave[triangleLineIndex][2]=inWave[i][j-1]
    triangleLineIndex+=1
    triangleWave[triangleLineIndex][0]=DimOffset(inWave,0)+(i-1)*DimDelta(inWave,0)
    triangleWave[triangleLineIndex][1]=DimOffset(inWave,1)+j*DimDelta(inWave,1)
    triangleWave[triangleLineIndex][2]=inWave[i-1][j]
    triangleLineIndex+=1
    triangleWave[triangleLineIndex][0]=DimOffset(inWave,0)+(i-1)*DimDelta(inWave,0)
    triangleWave[triangleLineIndex][1]=DimOffset(inWave,1)+(j-1)*DimDelta(inWave,1)
    triangleWave[triangleLineIndex][2]=inWave[i-1][j-1]
End


Function getBottomTriangle(inWave,i,j,triangleWave,triangleLineIndex)
    Wave inWave,triangleWave
    Variable i,j,triangleLineIndex

    // the lower triangle consists of (i,j-1),(i,j),(i-1,j)
    triangleWave[triangleLineIndex][0]=DimOffset(inWave,0)+i*DimDelta(inWave,0)
    triangleWave[triangleLineIndex][1]=DimOffset(inWave,1)+(j-1)*DimDelta(inWave,1)
    triangleWave[triangleLineIndex][2]=inWave[i][j-1]
    triangleLineIndex+=1
    triangleWave[triangleLineIndex][0]=DimOffset(inWave,0)+i*DimDelta(inWave,0)
    triangleWave[triangleLineIndex][1]=DimOffset(inWave,1)+j*DimDelta(inWave,1)
    triangleWave[triangleLineIndex][2]=inWave[i][j]
    triangleLineIndex+=1
    triangleWave[triangleLineIndex][0]=DimOffset(inWave,0)+(i-1)*DimDelta(inWave,0)
    triangleWave[triangleLineIndex][1]=DimOffset(inWave,1)+j*DimDelta(inWave,1)
    triangleWave[triangleLineIndex][2]=inWave[i-1][j]
End
AG,

Sorry for taking so long to get back. Thank you for posting your code, it was very helpful. I do need the triangle mesh with normals, so being able to do the calculations is important. My surface is relatively simple in that it can be described by a 2D array; it is clear where the outside of the surface lies.

I have also been able to write files for the stereo lithography and have had models created. This has worked out very well.

Thanks, again,

Jeff