Export RhinoCommon mesh as an STL file

117 Views Asked by At

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?

1

There are 1 best solutions below

0
Megidd On BEST ANSWER

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