rhino3d

Export RhinoCommon mesh as an STL file


I'm trying to export a Rhino.Geometry.Mesh as an STL file with simple and straightforward approach.

Tried

I tried this code:

        public static bool ExportMeshToStl(Mesh mesh, string fileName)
        {
            if (mesh == null || string.IsNullOrEmpty(fileName)) return false;

            // Create a temporary layer to isolate the specific mesh
            var tempLayer = new Layer { Name = "TempLayerForStlExport" };
            int tempLayerIndex = RhinoDoc.ActiveDoc.Layers.Add(tempLayer);
            if (tempLayerIndex < 0) return false;

            // Add the mesh to the temporary layer
            var attributes = new ObjectAttributes { LayerIndex = tempLayerIndex };
            var meshId = RhinoDoc.ActiveDoc.Objects.AddMesh(mesh, attributes);
            if (meshId == Guid.Empty)
            {
                RhinoDoc.ActiveDoc.Layers.Delete(tempLayerIndex, true);
                return false;
            }

            // Use RhinoApp.RunScript to export the mesh on the temporary layer as an STL file
            var script = $"_-Export \"{fileName}\" _Enter";
            RhinoDoc.ActiveDoc.Layers.SetCurrentLayerIndex(tempLayerIndex, true);
            bool result = RhinoApp.RunScript(script, false);

            // Remove the mesh and the temporary layer from the current document
            RhinoDoc.ActiveDoc.Objects.Delete(meshId, true);
            RhinoDoc.ActiveDoc.Layers.Delete(tempLayerIndex, true);

            return result;
        }

But MeshLab cannot open the resulted mesh:

Error: faulty mesh

Question

I couldn't find a straightforward & simple approach. Is there any?


Solution

  • Extract buffers

    I extract index and vertex buffers of a mesh by this method:

    
            public static void GetMeshBuffers(Mesh mesh, out float[] vertexBuffer, out int[] indexBuffer)
            {
                // Convert quads to triangles
                mesh.Faces.ConvertQuadsToTriangles();
    
                // Get vertex buffer
                Point3f[] vertices = mesh.Vertices.ToPoint3fArray();
                vertexBuffer = new float[vertices.Length * 3];
                for (int i = 0; i < vertices.Length; i++)
                {
                    vertexBuffer[i * 3] = vertices[i].X;
                    vertexBuffer[i * 3 + 1] = vertices[i].Y;
                    vertexBuffer[i * 3 + 2] = vertices[i].Z;
                }
    
                // Get index buffer
                MeshFace[] faces = mesh.Faces.ToArray();
                indexBuffer = new int[faces.Length * 3];
                for (int i = 0; i < faces.Length; i++)
                {
                    MeshFace face = faces[i];
                    indexBuffer[i * 3] = face.A;
                    indexBuffer[i * 3 + 1] = face.B;
                    indexBuffer[i * 3 + 2] = face.C;
                }
            }
    

    Save buffers as STL

    Then, I save the index and vertex buffers as STL by this method:

            public static void SaveBuffersAsStl(float[] vertexBuffer, int[] indexBuffer, string fileName)
            {
                // Open the file for writing
                using (FileStream fileStream = new FileStream(fileName, FileMode.Create))
                {
                    // Write the STL header
                    byte[] header = new byte[80];
                    fileStream.Write(header, 0, header.Length);
    
                    // Write the number of triangles
                    int triangleCount = indexBuffer.Length / 3;
                    byte[] triangleCountBytes = BitConverter.GetBytes(triangleCount);
                    fileStream.Write(triangleCountBytes, 0, 4);
    
                    // Write the triangles
                    for (int i = 0; i < indexBuffer.Length; i += 3)
                    {
                        // Get vertices for the current triangle
                        float x1 = vertexBuffer[indexBuffer[i] * 3];
                        float y1 = vertexBuffer[indexBuffer[i] * 3 + 1];
                        float z1 = vertexBuffer[indexBuffer[i] * 3 + 2];
                        float x2 = vertexBuffer[indexBuffer[i + 1] * 3];
                        float y2 = vertexBuffer[indexBuffer[i + 1] * 3 + 1];
                        float z2 = vertexBuffer[indexBuffer[i + 1] * 3 + 2];
                        float x3 = vertexBuffer[indexBuffer[i + 2] * 3];
                        float y3 = vertexBuffer[indexBuffer[i + 2] * 3 + 1];
                        float z3 = vertexBuffer[indexBuffer[i + 2] * 3 + 2];
    
                        // Compute the normal vector of the triangle
                        float nx = (y2 - y1) * (z3 - z1) - (z2 - z1) * (y3 - y1);
                        float ny = (z2 - z1) * (x3 - x1) - (x2 - x1) * (z3 - z1);
                        float nz = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);
                        float length = (float)Math.Sqrt(nx * nx + ny * ny + nz * nz);
                        nx /= length;
                        ny /= length;
                        nz /= length;
    
                        // Write the normal vector
                        byte[] normal = new byte[12];
                        BitConverter.GetBytes(nx).CopyTo(normal, 0);
                        BitConverter.GetBytes(ny).CopyTo(normal, 4);
                        BitConverter.GetBytes(nz).CopyTo(normal, 8);
                        fileStream.Write(normal, 0, normal.Length);
    
                        // Write the vertices in counter-clockwise order
                        byte[] triangle = new byte[36];
                        BitConverter.GetBytes(x1).CopyTo(triangle, 12);
                        BitConverter.GetBytes(y1).CopyTo(triangle, 16);
                        BitConverter.GetBytes(z1).CopyTo(triangle, 20);
                        BitConverter.GetBytes(x3).CopyTo(triangle, 0);
                        BitConverter.GetBytes(y3).CopyTo(triangle, 4);
                        BitConverter.GetBytes(z3).CopyTo(triangle, 8);
                        BitConverter.GetBytes(x2).CopyTo(triangle, 24);
                        BitConverter.GetBytes(y2).CopyTo(triangle, 28);
                        BitConverter.GetBytes(z2).CopyTo(triangle, 32);
                        fileStream.Write(triangle, 0, triangle.Length);
    
                        // Write the triangle attribute (zero)
                        byte[] attribute = new byte[2];
                        fileStream.Write(attribute, 0, attribute.Length);
                    }
                }
            }
    

    Call methods

    The above methods are called like this:

                RhinoObject obj = GetSingleMesh();
                // Check if object is valid.
    
                Mesh mesh = obj.Geometry as Mesh;
                // Check if mesh is valid.
    
                // Extract vertex buffer and index buffer.
                float[] vertexBuffer;
                int[] indexBuffer;
                GetMeshBuffers(mesh, out vertexBuffer, out indexBuffer);
    
                SaveBuffersAsStl(vertexBuffer, indexBuffer, "mesh-out.stl");
    

    Test

    Tests indicate that the above methods work just fine.

    Saved output mesh