C# obj转stl

该代码示例展示了如何使用C#将OBJ模型数据转换为STL格式。它包含了对向量运算的支持以及读取OBJ文件和生成STL文件(支持ASCII和二进制格式)的函数。转换过程涉及到解析OBJ文件中的顶点和法线信息,并构造STL文件的三角面片。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

记录下,使用C#将obj模型转换成stl格式
完整代码如下

1、这个脚本定义使用到的部分结构

using System;
using System.Collections.Generic;

namespace ModelProcess
{
    class MathUtils
    {
        public class Vector3
        {
            public float x, y, z;
            public Vector3() { }
            public Vector3(float x, float y, float z)
            {
                this.x = x;
                this.y = y;
                this.z = z;
            }

            public static Vector3 Min
            {
                get
                {
                    return new Vector3(float.MinValue, float.MinValue, float.MinValue);
                }
            }

            public static Vector3 Max
            {
                get
                {
                    return new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
                }
            }

            public static Vector3 operator +(Vector3 a, Vector3 b)
            {
                return new Vector3(a.x + b.x, a.y + b.y, a.z + b.z);
            }

            public static Vector3 operator -(Vector3 a, Vector3 b)
            {
                return new Vector3(a.x - b.x, a.y - b.y, a.z - b.z);
            }

            public static Vector3 operator *(Vector3 v, float a)
            {
                return new Vector3(v.x * a, v.y * a, v.z * a);
            }

            public static Vector3 operator /(Vector3 v, float a)
            {
                if (a == 0) throw new Exception("除数不能为0!");
                return new Vector3(v.x / a, v.y / a, v.z / a);
            }

            /// <summary>
            /// 获取向量长度
            /// </summary>
            /// <returns></returns>
            public float Length()
            {
                return (float)Math.Sqrt(x * x + y * y + z * z);
            }

            /// <summary>
            /// 向量单位化
            /// </summary>
            /// <returns></returns>
            public Vector3 Normalize()
            {
                float length = Length();
                if (length == 0) return new Vector3(x, y, z);
                return new Vector3(x / length, y / length, z / length);
            }

            /// <summary>
            /// 获取两个向量中的更小元素,用于获取包围盒
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static Vector3 GetMaxComponent(Vector3 a, Vector3 b)
            {
                return new Vector3(Math.Max(a.x, b.x), Math.Max(a.y, b.y), Math.Max(a.z, b.z));
            }

            /// <summary>
            /// 获取两个向量中的更大元素,用于获取包围盒
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static Vector3 GetMinComponent(Vector3 a, Vector3 b)
            {
                return new Vector3(Math.Min(a.x, b.x), Math.Min(a.y, b.y), Math.Min(a.z, b.z));
            }

            /// <summary>
            /// 向量叉乘
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static Vector3 Cross(Vector3 a, Vector3 b)
            {

                if (Equals(Dot(a, b), 1.0)) return new Vector3(0, 0, 0);

                Vector3 res = new Vector3
                {
                    x = a.y * b.z - a.z * b.y,
                    y = a.z * b.x - a.x * b.z,
                    z = a.x * b.y - a.y * b.x
                };
                return res;
            }

            public static float Dot(Vector3 a, Vector3 b)
            {
                return a.x * b.x + a.y * b.y + a.z * b.z;
            }

            public override string ToString()
            {
                return "x : " + x + "  y : " + y + "  z : " + z;
            }
        }

        public class Vector2
        {
            public float x, y;
            public Vector2() { }
            public Vector2(float x,float y)
            {
                this.x = x;
                this.y = y;
            }
        }

        public class Triangle
        {
            // vertex
            public int a;
            public int b;
            public int c;
            // normal
            public int an;
            public int bn;
            public int cn;
            // uv
            public int au;
            public int bu;
            public int cu;

            public bool hasNormal;
            public bool hasUv;
        }

        public class Group
        {
            public string groupName;
            public string mtlName;
            public List<Triangle> triangles;
        }

        public class ColladaMaterial
        {
            public Vector3 diffuse;
            public Vector3 emissive;
            public string diffuseMap;

            public ColladaMaterial()
            {
                diffuse = new Vector3(1, 1, 1);
                emissive = new Vector3(0, 0, 0);
                diffuseMap = "";
            }
        }
    }
}

2、这个脚本定义了将obj转换为stl的函数,调用方式为ExportStlFromObj.Export(objPath,outputPath),记得引入命名空间

using System.Collections.Generic;
using System.IO;
using System.Text;
using static ModelProcess.MathUtils;

namespace ModelProcess
{
    class ExportStlFromObj
    {
        /// <summary>
        /// 将obj转换为stl
        /// </summary>
        /// <param name="objPath">obj模型的完整路径</param>
        /// <param name="outputPath">stl的完整路径,包含后缀</param>
        /// <param name="isBinary">是否以二进制格式导出</param>
        public static void Export(string objPath, string outputPath, bool isBinary = true)
        {
            List<Vector3> vertices = new List<Vector3>();
            List<Vector3> normals = new List<Vector3>();
            List<Triangle> triangles = new List<Triangle>();
            int count = 0;
            {
                FileStream fs = new FileStream(objPath, FileMode.Open, FileAccess.Read);
                StreamReader sr = new StreamReader(fs);
                string line = "";
                while (!sr.EndOfStream)
                {
                    line = Utils.GetStrFields(sr.ReadLine()).Trim();
                    if (line.StartsWith("v "))
                    {
                        string[] strs = line.Split(' ');
                        if (strs.Length >= 4)
                        {
                            vertices.Add(new Vector3(float.Parse(strs[1]), float.Parse(strs[2]), float.Parse(strs[3])));
                        }
                    }
                    else if (line.StartsWith("vn "))
                    {
                        string[] strs = line.Split(' ');
                        if (strs.Length >= 4)
                        {
                            normals.Add(new Vector3(float.Parse(strs[1]), float.Parse(strs[2]), float.Parse(strs[3])));
                        }
                    }
                    else if (line.StartsWith("f "))
                    {
                        string[] strs = line.Split(' ');
                        count += strs.Length - 3;
                        string[] first = strs[1].Split('/');
                        int avi = int.Parse(first[0]);
                        int ani = 0;
                        if (first.Length >= 3 && !string.IsNullOrEmpty(first[2]))
                        {
                            ani = int.Parse(first[2]);
                        }
                        for (int k = 2; k < strs.Length - 1; k++)
                        {
                            string[] second = strs[k].Split('/');
                            string[] third = strs[k + 1].Split('/');
                            int bvi = int.Parse(second[0]);
                            int cvi = int.Parse(third[0]);
                            Triangle t = new Triangle() { a = avi, b = bvi, c = cvi };
                            if (ani != 0)
                            {
                                t.an = ani;
                                t.bn = int.Parse(second[2]);
                                t.cn = int.Parse(third[2]);
                                t.hasNormal = true;
                            }
                            else
                            {
                                t.hasNormal = false;
                            }
                            triangles.Add(t);
                        }
                    }
                }
                sr.Close();
                fs.Close();
            }

            if (isBinary)
            {
                ExportBinary(vertices, normals, triangles, Path.GetFileNameWithoutExtension(objPath), outputPath, count);
            }
            else
            {
                ExportAscii(vertices, normals, objPath, outputPath);
            }
            vertices.Clear();
            normals.Clear();
            triangles.Clear();
        }

        private static void ExportBinary(List<Vector3> vertices, List<Vector3> normals, List<Triangle> triangles, string name, string outputPath, int count)
        {
            FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
            BinaryWriter bw = new BinaryWriter(fs, Encoding.UTF8);
            //文件的起始80字节是文件头存储零件名,可以放入任何文字信息
            bw.Write(Path.GetFileNameWithoutExtension(name));
            bw.Seek(80, SeekOrigin.Begin);
            //紧随着用4个字节的整数来描述实体的三角面片个数
            bw.Write(count);

            int avi, bvi, cvi;
            int ani, bni, cni;
            Vector3 a, b, c;
            Vector3 n;
            foreach (Triangle t in triangles)
            {
                if (t.a < 0) avi = vertices.Count + t.a;
                else avi = t.a - 1;
                if (t.b < 0) bvi = vertices.Count + t.b;
                else bvi = t.b - 1;
                if (t.c < 0) cvi = vertices.Count + t.c;
                else cvi = t.c - 1;
                a = vertices[avi];
                b = vertices[bvi];
                c = vertices[cvi];
                if (t.hasNormal)
                {
                    if (t.an < 0) ani = normals.Count + t.an;
                    else ani = t.an - 1;
                    if (t.bn < 0) bni = normals.Count + t.bn;
                    else bni = t.bn - 1;
                    if (t.cn < 0) cni = normals.Count + t.cn;
                    else cni = t.cn - 1;
                    n = (normals[ani] + normals[bni] + normals[cni]) / 3;
                }
                else
                {
                    Vector3 ab = b - a;
                    Vector3 ac = c - a;
                    n = Vector3.Cross(ab, ac).Normalize();
                }

                bw.Write(n.x);
                bw.Write(n.y);
                bw.Write(n.z);

                bw.Write(a.x);
                bw.Write(a.y);
                bw.Write(a.z);
                bw.Write(b.x);
                bw.Write(b.y);
                bw.Write(b.z);
                bw.Write(c.x);
                bw.Write(c.y);
                bw.Write(c.z);

                //填充两个字节  三角面片的最后2个字节用来描述三角面片的属性信息(包括颜色属性等)暂时没有用
                bw.Seek(2, SeekOrigin.Current);
            }

            bw.Close();
            fs.Close();
        }

        private static void ExportAscii(List<Vector3> vertices, List<Vector3> normals, string objPath, string outputPath)
        {
            FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
            StreamWriter sw = new StreamWriter(fs, Encoding.UTF8);

            FileStream objFs = new FileStream(objPath, FileMode.Open, FileAccess.Read);
            StreamReader sr = new StreamReader(objFs);
            string line = "";
            string currenGroupName = "";
            int avi, ani, bvi, bni, cvi, cni;
            Vector3 a, b, c, n;
            while (!sr.EndOfStream)
            {
                line = Utils.GetStrFields(sr.ReadLine()).Trim();
                if(line.StartsWith("g "))
                {
                    if (!string.IsNullOrEmpty(currenGroupName))
                    {
                        sw.WriteLine("endsolid " + currenGroupName);
                    }
                    if (line.Length <= 2) currenGroupName = Utils.GetTimestamp();
                    else currenGroupName = Utils.ReplaceAll(line.Substring(2), " ", "_");
                    sw.WriteLine("solid " + currenGroupName);
                }
                if (line.StartsWith("f "))
                {
                    string[] strs = line.Split(' ');
                    string[] first = strs[1].Split('/');
                    avi = int.Parse(first[0]);
                    if (avi < 0) avi += vertices.Count;
                    else avi -= 1;
                    ani = 0;
                    if (first.Length >= 3 && !string.IsNullOrEmpty(first[2]))
                    {
                        ani = int.Parse(first[2]);
                    }
                    for (int k = 2; k < strs.Length - 1; k++)
                    {
                        string[] second = strs[k].Split('/');
                        string[] third = strs[k + 1].Split('/');
                        bvi = int.Parse(second[0]);
                        if (bvi < 0) bvi += vertices.Count;
                        else bvi -= 1;
                        cvi = int.Parse(third[0]);
                        if (cvi < 0) cvi += vertices.Count;
                        else cvi -= 1;

                        a = vertices[avi];
                        b = vertices[bvi];
                        c = vertices[cvi];

                        if (ani != 0)
                        {
                            if (ani < 0) ani += normals.Count;
                            else ani -= 1;
                            bni = int.Parse(second[2]);
                            if (bni < 0) bni += normals.Count;
                            else bni -= 1;
                            cni = int.Parse(third[2]);
                            if (cni < 0) cni += normals.Count;
                            else cni -= 1;
                            n = (normals[ani] + normals[bni] + normals[cni]) / 3;
                        }
                        else
                        {
                            Vector3 ab = b - a;
                            Vector3 ac = c - a;
                            n = Vector3.Cross(ab, ac).Normalize();
                        }
                        sw.WriteLine("\tfacet normal " + n.x + " " + n.y + " " + n.z);
                        sw.WriteLine("\t\touter loop");
                        sw.WriteLine("\t\t\tvertex " + a.x + " " + a.y + " " + a.z);
                        sw.WriteLine("\t\t\tvertex " + b.x + " " + b.y + " " + b.z);
                        sw.WriteLine("\t\t\tvertex " + c.x + " " + c.y + " " + c.z);
                        sw.WriteLine("\t\tendloop");
                        sw.WriteLine("\tendfacet");
                    }
                }
            }
            sr.Close();
            objFs.Close();
            sw.WriteLine("endsolid " + currenGroupName);
            sw.Close();
            fs.Close();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值