STL文件规则
(1)共顶点规则
每一个三角面片必须与其相邻的每一个面片共两个顶点 ,即一个三角面片的顶点不能落在相邻的任何三角面片的边上;
(2)取向规则
单个面片法向量符合右手法(ccw)则且其法向量必须指向实体外面;
(3)充满规则
小三角面片必须布满三维模型的所有表面,不得有任何遗漏;
(4)取值规则
每个顶点的坐标值必须为非负 ,即 STL 文件的实体应该在坐标系的第一象限.
二进制STL文件
二进制STL文件用固定的字节数来给出三角面片的几何信息。
【80】文件起始的80个字节是文件头,用于存贮文件名
【4】紧接着用 4 个字节的整数来描述模型的三角面片个数(小端存储)
【50*n】后面逐个给出每个三角面片的几何信息。每个三角面片占用固定的50个字节(小端存储),依次是:
【12】3个4字节浮点数(角面片的法矢量)
【12】3个4字节浮点数(1个顶点的坐标)
【12】3个4字节浮点数(2个顶点的坐标)
【12】3个4字节浮点数(3个顶点的坐标)个
【2】三角面片的最后2个字节用来描述三角面片的属性信息。
ASCII文件格式
ASCII码格式的STL文件逐行给出三角面片的几何信息,每一行以1个或2个关键字开头。在STL文件中的三角面片的信息单元 facet 是一个带矢量方向的三角面片,STL 三维模型就是由一系列这样的三角面片构成。整个STL文件首行给出了文件路径及文件名。在一个 STL 文件中,每一个 facet 由 7 行数据组成,facet normal是三角面片指向实体外部的法矢量坐标,outer loop说明随后的3行数据分别是三角面片的 3 个顶点坐标,3 顶点沿指向实体外部的法矢量方向逆时针排列。
solid filename stl //自定义文件头
facet normal x y z //三角面片法向量的3个
outer loop
vertex x y z //三角面片第一个顶点坐标
vertex x y z //三角面片第二个顶点坐标
vertex x y z //三角面片第三个顶点坐标
endloop
endfacet //完成一个三角面片定义
……
endsolid filename stl ∥整个STL文件定义结束
注意
stl文件包含了一些错误信息
判断stl文件并读取
这里学习消化了MeshLab中的代码,MeshLab也使用了别的库,记不清是看的别的库还是MeshLab自己实现的代码了。
判断stl文件是二进制还是ASCII码
bool isSTLBinary(const char *filename)
{
bool isBinary = false;//return value
FILE *fp = nullptr;
int errorCode = fopen_s(&fp,filename, "r");
if (errorCode==0)//成功打开文件
{
//确定文件实际大小
fseek(fp, 0, SEEK_END);//将fp移动到文件尾部
int fileSize = ftell(fp);//返回文档首部到fp位置大小(bytes)
int facetNum;
//计算标准stl二进制文件的大小
fseek(fp, STL_LABEL_SIZE, SEEK_SET);//跳过文档开头的注释
fread(&facetNum, sizeof(int), 1, fp);//读取facet的数目并保存在facetNum中
int standardBinaryFileSize = 80 + sizeof(int)+facetNum * 50;
//判断是否是标准stl文件
if (fileSize==standardBinaryFileSize)
{
isBinary = true;
}
//判断是否是非标准stl文件
unsigned char tmpbuf[128];//如果文件过短,这里会有Bug
fread(tmpbuf, sizeof(tmpbuf), 1, fp);//读取128个char大小的数据
for (unsigned int i = 0;i<sizeof(tmpbuf);i++)
{
//char类型取值范围是-128~127,如果是ASCII格式,应该全是char
if (tmpbuf[i]>127)
{
isBinary = true;
break;
}
}
fclose(fp);
}
return isBinary;
}
读取二进制stl文件
先定义一个表示stl文件的类。这个类其实可以不要,可以按照自己的实际需求把从stl文件中读取的数据组织起来。比如说可以只读取其中的顶点信息而忽略法向量信息,我这里没有读取属性信息。
class STLDocument
{
public:
class STLPoint
{
public:
double x;
double y;
double z;
explicit STLPoint()
{
x = y = z = 0;
}
explicit STLPoint(float *p_Array)
{
x = p_Array[0];
y = p_Array[1];
z = p_Array[2];
}
STLPoint& operator=(float *p_Array)
{
x = p_Array[0];
y = p_Array[1];
z = p_Array[2];
return *this;
}
};
class STLFacet
{
public:
STLPoint m_PointList[3];//三个顶点
STLPoint m_Normal;//法向量
};
vector<STLPoint> m_VertexList;
vector<STLFacet> m_FacetList;
void clear(); //清除数据
};//end class
读取二进制stl文件,并将数据存在上面定义的类里面。
const int STL_LABEL_SIZE = 80;
bool openBinary(const std::string &p_FileName, STLDocument &p_STLDocument)
{
p_STLDocument.clear();
FILE *fp;
int numFacet;
int error= fopen_s(&fp,p_FileName.c_str(), "rb");
float normal[3];
float point1[3];
STLDocument::STLPoint stlPoint;
STLDocument::STLFacet stlFacet;
if (0==error)//成功打开文件
{
fseek(fp, STL_LABEL_SIZE, SEEK_SET);//跳过开头
fread(&numFacet, sizeof(int), 1, fp);//读取facet的数目
for (int facetIndex = 0; facetIndex < numFacet;++facetIndex)
{
unsigned short attr; //用来储存属性,实际上把这个值丢弃了
fread(normal, sizeof(float), 3, fp);//读取facet的法向量
stlFacet.m_Normal = normal;
for (int i = 0; i < 3;++i)
{
fread(point1, sizeof(float), 3, fp);//读取vertex
stlFacet.m_PointList[i] = point1;
stlPoint = point1;
p_STLDocument.m_VertexList.push_back(stlPoint);
}
fread(&attr, sizeof(unsigned short), 1, fp);//读取属性
p_STLDocument.m_FacetList.push_back(stlFacet);
}
}
fclose(fp);
return true;
}
读取ASCII码的stl文件
bool openASCII(const std::string &p_FileName, STLDocument &p_STLDocument)
{
p_STLDocument.clear();
ifstream ifs;
ifs.open(p_FileName, ifstream::in);
if (ifs.is_open())
{
/*略去第一行*/
string line;
getline(ifs, line);
/*读取facet*/
int readCounter = 0;//记录每行读取成功的数据项
int lineCounter = 0;
while (!ifs.eof())
{
STLDocument::STLFacet facet;
STLDocument::STLPoint point;
/*读取facet normal ni nj nk*/
getline(ifs, line);
readCounter = sscanf(line.c_str(), "%*s %*s %lf %lf %lf\n", &facet.m_Normal.x, &facet.m_Normal.y, &facet.m_Normal.z);
if (readCounter!=3)
{
// we could be in the case of a multiple solid object, where after a endfaced instead of another facet we have to skip two lines:
// endloop
// endfacet
//endsolid <- continue on ret==0 will skip this line
//solid ascii <- and this one.
// facet normal 0.000000e+000 7.700727e-001 -6.379562e-001
lineCounter++;
continue;
}//end if
getline(ifs, line);//读取outer loop
/*读取三个顶点坐标vertex v1x v1y v1z*/
for (int i = 0; i < 3;i++)
{
getline(ifs, line);
readCounter=sscanf(line.c_str(),"%*s %lf %lf %lf", &point.x, &point.y, &point.z);
p_STLDocument.m_VertexList.push_back(point);
facet.m_PointList[i] = point;
}
p_STLDocument.m_FacetList.push_back(facet);
getline(ifs, line);//读取“endloop”
getline(ifs, line);//读取"endfacet"
lineCounter += 7;
}// end while (ifs.eof())
}//end if (ifs.is_open())
else
{
cout << "Error Opening file!"<<endl;
return false;
}
ifs.close();
return true;
}