1. 提取轮廓算法
1.1 读取2D STL 三角面片边信息
在由三角面片组成的多边形中,轮廓边之会出现一次,而重合的边会出现两次,根据这个,就可以读取边的信息,如图
而三角面片STL格式如下, 其中facet normal是平面的法向量,在2D平面这个法向量可以忽略
vertex表示三角形的三个顶点
facet normal 0 0 1
outer loop
vertex 0 0 0
vertex 1 0 0
vertex 0 1 0
endloop
endfacet
那么基本算法就可以这样设计
1. 读取2D STL 文件的三个顶点,将三个顶点组成边,记录边出现的次数(使用unordered_map)
2. 遍历所有组成的边,出现次数为2的说明是重复边,舍去
算法是简单的,下面看实现细节
首先是2D点数据结构实现,细节上需要注意赋值运算符=和相等运算符==。同时由于后面的设计需要对点使用unordered_map,因此需要设计operator()
struct Point2D
{
double x, y;
Point2D() : x(0.0), y(0.0){}
Point2D(double x_, double y_) : x(x_), y(y_){}
Point2D(const Point2D& p) : x(p.x), y(p.y){}
Point2D& operator=(const Point2D& p)
{
x = p.x;
y = p.y;
return *this;
}
};
bool operator==(const Point2D& p1, const Point2D& p2)
{
return (p1.x == p2.x && p1.y == p2.y);
}
// 用于哈希 Point2D
namespace std {
template<>
struct hash<Point2D> {
std::size_t operator()(const Point2D& p) const {
return std::hash<double>()(p.x) ^ std::hash<double>()(p.y);
}
};
}
接着是2D线段数据结构的实现,由于线段需要使用unordered_map,因此需要设计operator()和==运算符。需要注意,线段是没有方向的,因此==运算符需要考虑两种情况
struct Segment2D
{
Point2D P1, P2;
Segment2D() : P1(), P2(){};
Segment2D(const Point2D& _P1, const Point2D& _P2){
P1 = _P1; P2 = _P2;
}
Segment2D(const Segment2D& segment){
P1 = segment.P1;
P2 = segment.P2;
}
Segment2D& operator=(const Segment2D& segment){
P1 = segment.P1;
P2 = segment.P2;
return *this;
}
};
bool operator==(const Segment2D& segment1, const Segment2D& segment2)
{
return (segment1.P1 == segment2.P1 && segment1.P2 == segment2.P2) ||
(segment1.P1 == segment2.P2 && segment1.P2 == segment2.P1);
}
// 特化 std::hash 模板
namespace std {
template<>
struct hash<Segment2D> {
size_t operator()(const Segment2D& s) const {
// 假设我们用两个点的坐标组合来生成哈希值
auto h1 = std::hash<double>{}(s.P1.x);
auto h2 = std::hash<double>{}(s.P1.y);
auto h3 = std::hash<double>{}(s.P2.x);
auto h4 = std::hash<double>{}(s.P2.y);
// 使用 XOR 组合哈希值
return (h1 ^ (h2 << 1)) ^ (h3 ^ (h4 << 1));
}
};
}
然后是2D 线段提取的完整实现,这里的话我是以ASCII码形式读取的STL文件,感兴趣的小伙伴也可以改成读取二进制形式的STL
void readSTL2D(const std::string& filepath, std::vector<Point2D>& points, std::vector<Segment2D>& segments2D)
{
printf("Reading file \" %s \"...\n", filepath.c_str());
std::ifstream input(filepath);
if (!input.is_open())
{
printf("\nError: %s, %d.\n", __FILE__, __LINE__);
printf("Can not open file:\"%s\" .\n", filepath.c_str());
exit(EXIT_FAILURE);
}
std::string s;
while (input >> s)
{
if (s == "vertex")
{
double x, y, z;
input >> x >> y >> z;
points.push_back(Point2D(x, y));
}
}
if (points.size() % 3 != 0){
printf("Error %s, %d\ninput 2D stl is not illegal.\n", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
std::unordered_map<Segment2D, int> umap_segments2D;
for (int i=0; i<points.size(); i+=3){
const Point2D& p1 = points[i + 0];
const Point2D& p2 = points[i + 1];
const Point2D& p3 = points[i + 2];
Segment2D seg1(p1, p2);
Segment2D seg2(p2, p3);
Segment2D seg3(p3, p1);
umap_segments2D[seg1]++;
umap_segments2D[seg2]++;
umap_segments2D[seg3]++;
}
for (auto it = umap_segments2D.begin(); it != umap_segments2D.end(); ++it)
{
if (it->second != 1) continue;
segments2D.push_back(it->first);
}
printf("segments2D.size() = %zd\n", segments2D.size());
}
1.2 提取轮廓
在2.1中提取了线段之后,根据线段提取出每一段轮廓,包括外部和内部。
思路如下:
1. 选取一个起始线段,根据线段的两个端点,寻找下一个线段,将找过的节点进行记录。
当线段的最后一个点和起点重合时,那么这个回路轮廓就算找到了。
2. 找到这个轮廓之后,使用并查集算法,迭代访问当前节点的下一个节点,将每个节点记录到数组中,这样就完成了轮廓的提取
3. 再选取其他的线段作为其实线段,判断一下该线段的两个端点是否被记录,没有被记录那么就是别的轮廓,再按照1、2中的操作就行
1、2 步骤中的算法如下:
polygons 用于保存轮廓,start_seg_id即步骤1中提到的选取起始线段
void addOutLineWithStartSegId(std::vector<std::vector<Point2D>>& polygons,
int start_seg_id, const std::vector<Segment2D>& segments, std::unordered_map<Point2D, Point2D>& umap)
{
Point2D start_p = segments[start_seg_id].P1;
Point2D next_p = segments[start_seg_id].P2;
if (umap.find(start_p) != umap.end() || umap.find(next_p) != umap.end()) return;
umap[start_p] = next_p;
int i = 1;
for (int j = 0; j<segments.size(); ++j)
{
if (j == start_seg_id) continue;
Point2D P1 = segments[j].P1;
Point2D P2 = segments[j].P2;
if (P1 == next_p && umap.find(P2) == umap.end())
{
umap[next_p] = P2;
next_p = P2;
j = -1;
}
else if (P2 == next_p && umap.find(P1) == umap.end())
{
umap[next_p] = P1;
next_p = P1;
j = -1;
}
else if (P1 == next_p && P2 == start_p)
{
umap[P1] = P2;
break;
}
else if (P2 == next_p && P1 == start_p)
{
umap[P2] = P1;
break;
}
}
std::vector<Point2D> polygon;
polygon.push_back(start_p);
next_p = umap[start_p];
while ( !(next_p == start_p)){
polygon.push_back(next_p);
next_p = umap[next_p];
}
polygons.push_back(polygon);
}
3 步骤实现如下,由于要区分外部轮廓和内部轮廓,所以需要首先计算最外边的线段轮廓
void linkEdgesWithMap(
std::vector<std::vector<Point2D>>& polygons,
const std::vector<Segment2D>& segments,
bool is_print)
{
std::unordered_map<Point2D, Point2D> umap;
double max_X_axis = std::max(segments[0].P1.x, segments[0].P2.x);
int outer_start_seg_id = 0;
for (int i = 1; i<segments.size(); ++i)
{
const Point2D& P1 = segments[i].P1;
const Point2D& P2 = segments[i].P2;
if (P1.x > max_X_axis){
max_X_axis = P1.x;
outer_start_seg_id = i;
}
if (P2.x > max_X_axis)
{
max_X_axis = P2.x;
outer_start_seg_id = i;
}
}
addOutLineWithStartSegId(polygons, outer_start_seg_id, segments, umap);
for (int i=0; i<segments.size(); ++i)
{
if (i == outer_start_seg_id) continue;
addOutLineWithStartSegId(polygons, i, segments, umap);
}
if (umap.size() != segments.size())
{
printf("umap_size = %zd, segments.size() = %zd\n", umap.size(), segments.size());
printf("error: %d, %s\n", __LINE__, __FILE__);
printf("there is some error in the input model, check it if is closed.\n");
exit(EXIT_FAILURE);
}
if (is_print)
{
for (int i=0; i<polygons.size(); ++i)
{
printf("\n\n------------------------Polygon %d----------------------\n", i);
printf("polygon point size: %zd\n", polygons[i].size());
int j = 0;
for (const Point2D& point : polygons[i])
{
if (j != polygons[i].size()-1) printf("point(%lf, %lf)->", point.x, point.y);
else printf("point(%lf, %lf)", point.x, point.y);
if ((j+1) % 3 == 0) printf("\n");
j++;
}
printf("\n");
}
}
}
1.3 完整实现如下
#include <unordered_map>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
struct Point2D
{
double x, y;
Point2D() : x(0.0), y(0.0){}
Point2D(double x_, double y_) : x(x_), y(y_){}
Point2D(const Point2D& p) : x(p.x), y(p.y){}
Point2D& operator=(const Point2D& p)
{
x = p.x;
y = p.y;
return *this;
}
};
bool operator==(const Point2D& p1, const Point2D& p2)
{
return (p1.x == p2.x && p1.y == p2.y);
}
// 用于哈希 Point2D
namespace std {
template<>
struct hash<Point2D> {
std::size_t operator()(const Point2D& p) const {
return std::hash<double>()(p.x) ^ std::hash<double>()(p.y);
}
};
}
struct Segment2D
{
Point2D P1, P2;
Segment2D() : P1(), P2(){};
Segment2D(const Point2D& _P1, const Point2D& _P2){
P1 = _P1; P2 = _P2;
}
Segment2D(const Segment2D& segment){
P1 = segment.P1;
P2 = segment.P2;
}
Segment2D& operator=(const Segment2D& segment){
P1 = segment.P1;
P2 = segment.P2;
return *this;
}
};
bool operator==(const Segment2D& segment1, const Segment2D& segment2)
{
return (segment1.P1 == segment2.P1 && segment1.P2 == segment2.P2) ||
(segment1.P1 == segment2.P2 && segment1.P2 == segment2.P1);
}
// 特化 std::hash 模板
namespace std {
template<>
struct hash<Segment2D> {
size_t operator()(const Segment2D& s) const {
// 假设我们用两个点的坐标组合来生成哈希值
auto h1 = std::hash<double>{}(s.P1.x);
auto h2 = std::hash<double>{}(s.P1.y);
auto h3 = std::hash<double>{}(s.P2.x);
auto h4 = std::hash<double>{}(s.P2.y);
// 使用 XOR 组合哈希值
return (h1 ^ (h2 << 1)) ^ (h3 ^ (h4 << 1));
}
};
}
void readSTL2D(const std::string& filepath, std::vector<Point2D>& points, std::vector<Segment2D>& segments2D);
void linkEdgesWithMap(std::vector<std::vector<Point2D>>& polygons,
const std::vector<Segment2D>& segments, bool is_print = false);
void addOutLineWithStartSegId(std::vector<std::vector<Point2D>>& polygons,
int start_seg_id, const std::vector<Segment2D>& segments, std::unordered_map<Point2D, Point2D>& umap);
int main()
{
// const std::string input = "../test_cubic_huanti.stl";
const std::string input = "../Hollow_out_square.stl";
std::vector<Point2D> points;
std::vector<Segment2D> segments2D;
readSTL2D(input, points, segments2D);
std::vector<std::vector<Point2D>> polygons;
linkEdgesWithMap(polygons, segments2D, true);
}
void readSTL2D(const std::string& filepath, std::vector<Point2D>& points, std::vector<Segment2D>& segments2D)
{
printf("Reading file \" %s \"...\n", filepath.c_str());
std::ifstream input(filepath);
if (!input.is_open())
{
printf("\nError: %s, %d.\n", __FILE__, __LINE__);
printf("Can not open file:\"%s\" .\n", filepath.c_str());
exit(EXIT_FAILURE);
}
std::string s;
while (input >> s)
{
if (s == "vertex")
{
double x, y, z;
input >> x >> y >> z;
points.push_back(Point2D(x, y));
}
}
if (points.size() % 3 != 0){
printf("Error %s, %d\ninput 2D stl is not illegal.\n", __FILE__, __LINE__);
exit(EXIT_FAILURE);
}
std::unordered_map<Segment2D, int> umap_segments2D;
for (int i=0; i<points.size(); i+=3){
const Point2D& p1 = points[i + 0];
const Point2D& p2 = points[i + 1];
const Point2D& p3 = points[i + 2];
Segment2D seg1(p1, p2);
Segment2D seg2(p2, p3);
Segment2D seg3(p3, p1);
umap_segments2D[seg1]++;
umap_segments2D[seg2]++;
umap_segments2D[seg3]++;
}
for (auto it = umap_segments2D.begin(); it != umap_segments2D.end(); ++it)
{
if (it->second != 1) continue;
segments2D.push_back(it->first);
}
printf("segments2D.size() = %zd\n", segments2D.size());
}
void addOutLineWithStartSegId(std::vector<std::vector<Point2D>>& polygons,
int start_seg_id, const std::vector<Segment2D>& segments, std::unordered_map<Point2D, Point2D>& umap)
{
Point2D start_p = segments[start_seg_id].P1;
Point2D next_p = segments[start_seg_id].P2;
if (umap.find(start_p) != umap.end() || umap.find(next_p) != umap.end()) return;
umap[start_p] = next_p;
int i = 1;
for (int j = 0; j<segments.size(); ++j)
{
if (j == start_seg_id) continue;
Point2D P1 = segments[j].P1;
Point2D P2 = segments[j].P2;
if (P1 == next_p && umap.find(P2) == umap.end())
{
umap[next_p] = P2;
next_p = P2;
j = -1;
}
else if (P2 == next_p && umap.find(P1) == umap.end())
{
umap[next_p] = P1;
next_p = P1;
j = -1;
}
else if (P1 == next_p && P2 == start_p)
{
umap[P1] = P2;
break;
}
else if (P2 == next_p && P1 == start_p)
{
umap[P2] = P1;
break;
}
}
std::vector<Point2D> polygon;
polygon.push_back(start_p);
next_p = umap[start_p];
while ( !(next_p == start_p)){
polygon.push_back(next_p);
next_p = umap[next_p];
}
polygons.push_back(polygon);
}
void linkEdgesWithMap(
std::vector<std::vector<Point2D>>& polygons,
const std::vector<Segment2D>& segments,
bool is_print)
{
std::unordered_map<Point2D, Point2D> umap;
double max_X_axis = std::max(segments[0].P1.x, segments[0].P2.x);
int outer_start_seg_id = 0;
for (int i = 1; i<segments.size(); ++i)
{
const Point2D& P1 = segments[i].P1;
const Point2D& P2 = segments[i].P2;
if (P1.x > max_X_axis){
max_X_axis = P1.x;
outer_start_seg_id = i;
}
if (P2.x > max_X_axis)
{
max_X_axis = P2.x;
outer_start_seg_id = i;
}
}
addOutLineWithStartSegId(polygons, outer_start_seg_id, segments, umap);
for (int i=0; i<segments.size(); ++i)
{
if (i == outer_start_seg_id) continue;
addOutLineWithStartSegId(polygons, i, segments, umap);
}
if (umap.size() != segments.size())
{
printf("umap_size = %zd, segments.size() = %zd\n", umap.size(), segments.size());
printf("error: %d, %s\n", __LINE__, __FILE__);
printf("there is some error in the input model, check it if is closed.\n");
exit(EXIT_FAILURE);
}
if (is_print)
{
for (int i=0; i<polygons.size(); ++i)
{
printf("\n\n------------------------Polygon %d----------------------\n", i);
printf("polygon point size: %zd\n", polygons[i].size());
int j = 0;
for (const Point2D& point : polygons[i])
{
if (j != polygons[i].size()-1) printf("point(%lf, %lf)->", point.x, point.y);
else printf("point(%lf, %lf)", point.x, point.y);
if ((j+1) % 3 == 0) printf("\n");
j++;
}
printf("\n");
}
}
}
测试了两个stl文件, test_cubic_huanti.stl内容如下

输出结果如下:

第二个文件为Hollow_out_square.stl


test_cubic_huanti.stl
solid
facet normal 0 0 1
outer loop
vertex 0 0 0
vertex 1 0 0
vertex 0 1 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 1 0
vertex 1 1 0
vertex 1 0 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 1 0 0
vertex 2 0 0
vertex 1 1 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 1 1 0
vertex 2 1 0
vertex 2 0 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 2 0 0
vertex 3 0 0
vertex 2 1 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 3 0 0
vertex 3 1 0
vertex 2 1 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 1 0
vertex 1 1 0
vertex 0 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 2 0
vertex 1 1 0
vertex 1 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 2 2 0
vertex 2 1 0
vertex 3 1 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 3 1 0
vertex 3 2 0
vertex 2 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 2 0
vertex 0 3 0
vertex 1 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 3 0
vertex 1 3 0
vertex 1 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 1 2 0
vertex 1 3 0
vertex 2 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 1 3 0
vertex 2 3 0
vertex 2 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 2 2 0
vertex 2 3 0
vertex 3 2 0
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 2 3 0
vertex 3 3 0
vertex 3 2 0
endloop
endfacet
endsolid
Hollow_out_square.stl
solid square_with_holes
facet normal 0 0 1
outer loop
vertex 0 0 0
vertex 0 1 0
vertex 1 0 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 0 0
vertex 0 1 0
vertex 1 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 1 0
vertex 1 1 0
vertex 1 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 2 0
vertex 0 1 0
vertex 0 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 2 0
vertex 1 2 0
vertex 0 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 2 0
vertex 1 3 0
vertex 0 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 3 0
vertex 1 4 0
vertex 1 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 4 0
vertex 0 3 0
vertex 0 4 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 4 0
vertex 1 4 0
vertex 0 5 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 4 0
vertex 0 5 0
vertex 1 5 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 4 0
vertex 1 5 0
vertex 2 5 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 4 0
vertex 2 5 0
vertex 2 4 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 2 4 0
vertex 2 5 0
vertex 3 4 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 4 0
vertex 2 5 0
vertex 3 5 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 4 0
vertex 3 5 0
vertex 4 5 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 4 0
vertex 4 5 0
vertex 4 4 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 4 0
vertex 4 5 0
vertex 5 4 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 5 0
vertex 5 5 0
vertex 5 4 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 3 0
vertex 4 4 0
vertex 5 4 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 3 0
vertex 5 4 0
vertex 5 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 2 0
vertex 4 3 0
vertex 5 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 5 2 0
vertex 4 3 0
vertex 5 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 1 0
vertex 4 2 0
vertex 5 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 1 0
vertex 5 2 0
vertex 5 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 5 0 0
vertex 4 1 0
vertex 5 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 5 0 0
vertex 4 0 0
vertex 4 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4 0 0
vertex 3 0 0
vertex 4 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 0 0
vertex 3 1 0
vertex 4 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 0 0
vertex 2 1 0
vertex 3 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 2 0 0
vertex 2 1 0
vertex 3 0 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 2 0 0
vertex 1 0 0
vertex 2 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 0 0
vertex 1 1 0
vertex 2 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 1 0
vertex 2 1 0
vertex 3 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 2 1 0
vertex 2 2 0
vertex 3 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 2 0
vertex 2 3 0
vertex 2 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 1 2 0
vertex 1 3 0
vertex 2 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 2 2 0
vertex 2 3 0
vertex 3 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 2 0
vertex 2 3 0
vertex 3 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 2 0
vertex 3 3 0
vertex 4 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 3 2 0
vertex 4 3 0
vertex 4 2 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 2 3 0
vertex 3 4 0
vertex 3 3 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 2 3 0
vertex 2 4 0
vertex 3 4 0
endloop
endfacet
endsolid square_with_holes
1372

被折叠的 条评论
为什么被折叠?



