STL文件有两种文件格式,分别是二进制的stl和Ascii格式的stl。下面将针对这文件的两个格式,用C语言分别写出一段简单的示例代码。
一、读取二进制的STL文件
(1)二进制stl文件的结构
文件起始的80个字节是文件头,用于存贮文件名;
紧接着用 4 个字节的整数来描述模型的三角面片个数, 后面逐个给出每个三角面片的几何信息。每个三角面片占用固定的50个字节,依次是:
3个4字节浮点数(角面片的法矢量)
3个4字节浮点数(1个顶点的坐标)
3个4字节浮点数(2个顶点的坐标)
3个4字节浮点数(3个顶点的坐标)
三角面片的最后2个字节用来描述三角面片的属性信息,一般只保留位置,具体数据不用管。
一个完整二进制STL文件的大小为三角形面片数乘以 50再加上84个字节。
(2)C语言读取代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//文件头,共84字节
struct Head
{
char partName[80];//零件名称
int faceNum;//面的数目
};
//点,三个float类型的,大小为12字节
struct Point
{
float x;
float y;
float z;
};
//法线
struct Normal
{
float i;
float j;
float k;
};
//三角面,由一个法线,三个点,和一个两字节的保留项,一共50字节
struct Face
{
Normal normal;
Point p1;
Point p2;
Point p3;
char info[2];//保留数据,可以不用管
};
int main()
{
Head head;
FILE *fp;
char fileName[128];
printf("请输入文件名:\n");
gets_s(fileName);//控制台里输入文件名称
fp = fopen(fileName,"rb");
if (fp != NULL)
{
fread(head.partName, 80, 1, fp);//获取部件名
fread(&head.faceNum, 4, 1, fp);//获取三角面片的数目
}
Face *faces = (Face*)malloc(head.faceNum * sizeof(Face));//根据三角面片的数目,创建一个Face类型的数组
//循环读取三角面片数据
for (int i = 0;i < head.faceNum;i++)
{
fread(&faces[i].normal, 12, 1, fp);//读取法线数据
fread(&faces[i].p1, 12, 1, fp);//读取顶点1的数据
fread(&faces[i].p2, 12, 1, fp);//读取顶点2的数据
fread(&faces[i].p3, 12, 1, fp);//读取顶点3的数据
fread(&faces[i].info,2, 1, fp);//读取保留项数据,这一项一般没什么用,这里选择读取是为了移动文件指针
}
fclose(fp);
printf("STL文件解析如下:\n");
printf("三角形数目:");
printf("%d\n", head.faceNum);
printf("各面的顶点和法线数据如下:\n");
//在控制台上输出STL文件的数据
for (int i = 0;i < head.faceNum;i++)
{
printf("面%d\n", i + 1);
printf("法线:\n");
printf("%f %f %f\n", faces[i].normal.i, faces[i].normal.j, faces[i].normal.k);
printf("顶点数据:\n");
printf("%f %f %f\n", faces[i].p1.x, faces[i].p1.y, faces[i].p1.z);
printf("%f %f %f\n", faces[i].p2.x, faces[i].p2.y, faces[i].p2.z);
printf("%f %f %f\n", faces[i].p2.x, faces[i].p2.y, faces[i].p2.z);
}
return 0;
}
(3)程序运行效果
二、读取ASCII格式的stl文件
(1)ASCII格式的stl文件结构
ASCII码格式的STL文件逐行给出三角面片的几何信息,每一行以1个或2个关键字开头。 在STL文件中的三角面片的信息单元 facet 是一个带矢量方向的三角面片,
STL三维模型就是由一系列这样的三角面片构成。 整个STL文件的首行给出了文件路径及文件名。 在一个 STL文件中,每一个facet由7 行数据组成, facet normal 是
三角面片指向实体外部的法矢量坐标, outer loop 说明随后的3行数据分别是三角面片的3个顶点坐标,3顶点沿指向实体外部的法矢量方向逆时针排列。ASCII格式的
STL 文件结构如下:
solid filename//第一行,"solid"标识+上文件名,文件名可以是任意字符
facetnormal i j k//第二行,“facetnormal”标识+法线数据i,j,k
outorloop//第三行,“outorloop”标识
vertex x y z//第四行,“vertex”标识+第一个顶点数据x,y,z
vertex x y z//第五行,“vertex”标识+第二个顶点数据
vertex x y z//第六行,"vertex"标识+第三个顶点数据
endloop//第七行,“endloop”标识
endfacet//第八行,"endfacet"标识
、、、、//其他三角面
endsolid filename//文件末尾,“endsolid”标识+文件名
下图是一个实例,保存的是一个四面体的数据:
(2)C语言读取代码
#include<stdio.h>
#include<stdlib.h>
char partName[80];//文件头,
struct Normal
{
float i;
float j;
float k;
};
struct Vertrex
{
float x;
float y;
float z;
};
struct Face
{
Normal normal;
Vertrex vertex1;
Vertrex vertex2;
Vertrex vertex3;
};
//接收三角片面的链表结点
struct Faces
{
Face data;
Faces *pointer;
};
int main()
{
FILE *fp;
char fileName[64];
Faces *faces = (Faces*)malloc(sizeof(Faces));//链表的表头
Faces *p = faces;
Faces *q;
printf("请输入文件名:\n");
gets_s(fileName);
fp = fopen(fileName, "r");
if (fp)
{
char str[24];
fscanf(fp, "%s", str);//该代码的作用仅是移动文件指针,使指针跳过标识符“solid”,将文件指针移到文件名称的位置
fscanf(fp, "%s", partName);//读入文件名称
//循环读取三角片面的法线和顶点数据
while (!feof(fp))
{
p->pointer= (Faces*)malloc(sizeof(Faces));//创建新结点,用于接收数据
q = p;
p = p->pointer;
fscanf(fp, "%s", str);//使文件指针跳过标识符“facetnormal”,指到法线数据部分
fscanf(fp, "%f%f%f", &p->data.normal.i, &p->data.normal.j, &p->data.normal.k);//读取法线数据
fscanf(fp, "%s", str);//使文件指针跳过标识符“facetloop”
fscanf(fp, "%s", str);//使文件指针跳过标识符“vertex”,指到顶点1的数据部分
fscanf(fp, "%f%f%f", &p->data.vertex1.x, &p->data.vertex1.y, &p->data.vertex1.z);
fscanf(fp, "%s", str);//使文件指针跳过标识符“vertex”,指到顶点2的数据部分
fscanf(fp, "%f%f%f", &p->data.vertex2.x, &p->data.vertex2.y, &p->data.vertex2.z);
fscanf(fp, "%s", str);//使文件指针跳过标识符“vertex”,指到顶点3的数据部分
fscanf(fp, "%f%f%f", &p->data.vertex3.x, &p->data.vertex3.y, &p->data.vertex3.z);
fscanf(fp, "%s", str);//使文件指针跳过标识符“endloop”
fscanf(fp, "%s", str);//使文件指针跳过标识符“endfacet“
}
free(q->pointer);//由于文件末尾有一行字符”endsolid .....“,造成多创建了一个结点,该结点的数据是无用的,因此要释放
q->pointer = NULL;//最后一个结点的数据释放完之后,将指向最后结点的指针置为NULL,方便遍历链表
}
else
{
printf("打开文件失败!");
}
p = faces->pointer;//获取链表第二个结点的指针
while (p!=NULL)
{
//循环输出链表
printf("法线:\n");
printf("%f %f %f\n", p->data.normal.i, p->data.normal.j, p->data.normal.k);
printf("顶点:\n");
printf("%f %f %f\n", p->data.vertex1.x, p->data.vertex1.y, p->data.vertex1.z);
printf("%f %f %f\n", p->data.vertex2.x, p->data.vertex2.y, p->data.vertex2.z);
printf("%f %f %f\n", p->data.vertex3.x, p->data.vertex3.y, p->data.vertex3.z);
p = p->pointer;
}
return 0;
}
(3)程序运行效果