记录下,使用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();
}
}
}