gmsh-TingyuanDoc  0.1
An Open-Source Timing-driven Analytical Mixed-size FPGA Placer
GModelIO_STL.cpp
Go to the documentation of this file.
1 // Gmsh - Copyright (C) 1997-2022 C. Geuzaine, J.-F. Remacle
2 //
3 // See the LICENSE.txt file in the Gmsh root directory for license information.
4 // Please report all issues on https://gitlab.onelab.info/gmsh/gmsh/issues.
5 
6 #include <stdio.h>
7 #include <string>
8 #include <algorithm>
9 #include <sstream>
10 #include "GModel.h"
11 #include "OS.h"
12 #include "MLine.h"
13 #include "MTriangle.h"
14 #include "MQuadrangle.h"
15 #include "MVertexRTree.h"
16 #include "discreteFace.h"
17 #include "StringUtils.h"
18 #include "Context.h"
19 
20 static bool invalidChar(char c) { return !(c >= 32 && c <= 126); }
21 
22 int GModel::readSTL(const std::string &name, double tolerance)
23 {
24  FILE *fp = Fopen(name.c_str(), "rb");
25  if(!fp) {
26  Msg::Error("Unable to open file '%s'", name.c_str());
27  return 0;
28  }
29 
30  // store triplets of points for each solid found in the file
31  std::vector<std::vector<SPoint3> > points;
32  SBoundingBox3d bbox;
33  std::vector<std::string> names;
34 
35  // "solid", or binary data header
36  char buffer[256];
37  if(!fgets(buffer, sizeof(buffer), fp)) {
38  fclose(fp);
39  return 0;
40  }
41 
42  // SPoint3 p0(1.9e6, 4e6, 0);
43 
44  bool binary = strncmp(buffer, "solid", 5) && strncmp(buffer, "SOLID", 5);
45 
46  // ASCII STL
47  if(!binary) {
48  if(strlen(buffer) > 6)
49  names.push_back(&buffer[6]);
50  else
51  names.push_back("");
52  points.resize(1);
53  while(!feof(fp)) {
54  // "facet normal x y z" or "endsolid"
55  if(!fgets(buffer, sizeof(buffer), fp)) break;
56  if(!strncmp(buffer, "endsolid", 8) || !strncmp(buffer, "ENDSOLID", 8)) {
57  // "solid"
58  if(!fgets(buffer, sizeof(buffer), fp)) break;
59  if(!strncmp(buffer, "solid", 5) || !strncmp(buffer, "SOLID", 5)) {
60  if(strlen(buffer) > 6)
61  names.push_back(&buffer[6]);
62  else
63  names.push_back("");
64  points.resize(points.size() + 1);
65  // "facet normal x y z"
66  if(!fgets(buffer, sizeof(buffer), fp)) break;
67  }
68  }
69  // "outer loop"
70  if(!fgets(buffer, sizeof(buffer), fp)) break;
71  // "vertex x y z"
72  for(int i = 0; i < 3; i++) {
73  if(!fgets(buffer, sizeof(buffer), fp)) break;
74  char s1[256];
75  double x, y, z;
76  if(sscanf(buffer, "%s %lf %lf %lf", s1, &x, &y, &z) != 4) break;
77  SPoint3 p(x, y, z);
78  // p -= p0;
79  points.back().push_back(p);
80  bbox += p;
81  }
82  // "endloop"
83  if(!fgets(buffer, sizeof(buffer), fp)) break;
84  // "endfacet"
85  if(!fgets(buffer, sizeof(buffer), fp)) break;
86  }
87  }
88 
89  // check if we could parse something
90  bool empty = true;
91  for(std::size_t i = 0; i < points.size(); i++) {
92  if(points[i].size()) {
93  empty = false;
94  break;
95  }
96  }
97  if(empty) points.clear();
98 
99  // binary STL (we also try to read in binary mode if the header told
100  // us the format was ASCII but we could not read any vertices)
101  if(binary || empty) {
102  if(binary)
103  Msg::Info("Mesh is in binary format");
104  else
105  Msg::Info("Wrong ASCII header or empty file: trying binary read");
106  rewind(fp);
107  while(!feof(fp)) {
108  char header[80];
109  if(!fread(header, sizeof(char), 80, fp)) break;
110  unsigned int nfacets = 0;
111  size_t ret = fread(&nfacets, sizeof(unsigned int), 1, fp);
112  bool swap = false;
113  if(nfacets > 100000000) {
114  Msg::Info("Swapping bytes from binary file");
115  swap = true;
116  SwapBytes((char *)&nfacets, sizeof(unsigned int), 1);
117  }
118  if(ret && nfacets) {
119  names.push_back(header);
120  points.resize(points.size() + 1);
121  char *data = new char[nfacets * 50 * sizeof(char)];
122  ret = fread(data, sizeof(char), nfacets * 50, fp);
123  if(ret == nfacets * 50) {
124  for(std::size_t i = 0; i < nfacets; i++) {
125  float *xyz = (float *)&data[i * 50 * sizeof(char)];
126  if(swap) SwapBytes((char *)xyz, sizeof(float), 12);
127  for(int j = 0; j < 3; j++) {
128  SPoint3 p(xyz[3 + 3 * j], xyz[3 + 3 * j + 1], xyz[3 + 3 * j + 2]);
129  // p -= p0;
130  points.back().push_back(p);
131  bbox += p;
132  }
133  }
134  }
135  delete[] data;
136  }
137  }
138  }
139 
140  // cleanup names
141  if(names.size() != points.size()) {
142  Msg::Debug("Invalid number of names in STL file - should never happen");
143  names.resize(points.size());
144  }
145  for(std::size_t i = 0; i < names.size(); i++) {
146  names[i].erase(remove_if(names[i].begin(), names[i].end(), invalidChar),
147  names[i].end());
148  }
149 
150  std::vector<GFace *> faces;
151  for(std::size_t i = 0; i < points.size(); i++) {
152  if(points[i].empty()) {
153  Msg::Error("No facets found in STL file for solid %d %s", i,
154  names[i].c_str());
155  fclose(fp);
156  return 0;
157  }
158  if(points[i].size() % 3) {
159  Msg::Error("Wrong number of points (%d) in STL file for solid %d %s",
160  points[i].size(), i, names[i].c_str());
161  fclose(fp);
162  return 0;
163  }
164  Msg::Info("%d facets in solid %d %s", points[i].size() / 3, i,
165  names[i].c_str());
166  // create face
167  GFace *face = new discreteFace(this, getMaxElementaryNumber(2) + 1);
168  faces.push_back(face);
169  add(face);
170  if(!names[i].empty()) setElementaryName(2, face->tag(), names[i]);
171  }
172 
173  // create triangles using unique vertices
174  double eps = norm(SVector3(bbox.max(), bbox.min())) * tolerance;
175  std::vector<MVertex *> vertices;
176  for(std::size_t i = 0; i < points.size(); i++)
177  for(std::size_t j = 0; j < points[i].size(); j++)
178  vertices.push_back(
179  new MVertex(points[i][j].x(), points[i][j].y(), points[i][j].z()));
180  MVertexRTree pos(eps);
181  pos.insert(vertices);
182 
183  std::set<MFace, MFaceLessThan> unique;
184  int nbDuplic = 0, nbDegen = 0;
185  for(std::size_t i = 0; i < points.size(); i++) {
186  for(std::size_t j = 0; j < points[i].size(); j += 3) {
187  MVertex *v[3];
188  for(int k = 0; k < 3; k++) {
189  double x = points[i][j + k].x();
190  double y = points[i][j + k].y();
191  double z = points[i][j + k].z();
192  v[k] = pos.find(x, y, z);
193  if(!v[k])
194  Msg::Error("Could not find node at position (%.16g, %.16g, %.16g) "
195  "with tol=%.16g",
196  x, y, z, eps);
197  }
198  if(!v[0] || !v[1] || !v[2]) {
199  // error
200  }
201  else if(v[0] == v[1] || v[0] == v[2] || v[1] == v[2]) {
202  Msg::Debug("Skipping degenerated triangle %lu %lu %lu", v[0]->getNum(),
203  v[1]->getNum(), v[2]->getNum());
204  nbDegen++;
205  }
206  else if(CTX::instance()->mesh.stlRemoveDuplicateTriangles) {
207  MFace mf(v[0], v[1], v[2]);
208  if(unique.find(mf) == unique.end()) {
209  faces[i]->triangles.push_back(new MTriangle(v[0], v[1], v[2]));
210  unique.insert(mf);
211  }
212  else {
213  nbDuplic++;
214  }
215  }
216  else {
217  faces[i]->triangles.push_back(new MTriangle(v[0], v[1], v[2]));
218  }
219  }
220  }
221  if(nbDuplic || nbDegen)
222  Msg::Warning("%d duplicate/%d degenerate triangles in STL file", nbDuplic,
223  nbDegen);
224 
226 
227  _storeVerticesInEntities(vertices); // will delete unused vertices
228 
229  fclose(fp);
230  return 1;
231 }
232 
233 static void writeSTLfaces(FILE *fp, std::vector<GFace *> &faces, bool binary,
234  double scalingFactor, const std::string &name)
235 {
236  bool useGeoSTL = false;
237  unsigned int nfacets = 0;
238  for(auto it = faces.begin(); it != faces.end(); ++it) {
239  nfacets += (*it)->triangles.size() + 2 * (*it)->quadrangles.size();
240  }
241  if(!nfacets) { // use CAD STL if there is no mesh
242  useGeoSTL = true;
243  for(auto it = faces.begin(); it != faces.end(); ++it) {
244  (*it)->buildSTLTriangulation();
245  nfacets += (*it)->stl_triangles.size() / 3;
246  }
247  }
248 
249  if(!binary) { fprintf(fp, "solid %s\n", name.c_str()); }
250  else {
251  char header[80];
252  strncpy(header, name.c_str(), 79);
253  header[79] = '\0';
254  fwrite(header, sizeof(char), 80, fp);
255  fwrite(&nfacets, sizeof(unsigned int), 1, fp);
256  }
257 
258  for(auto it = faces.begin(); it != faces.end(); ++it) {
259  if(useGeoSTL && (*it)->stl_vertices_uv.size()) {
260  for(std::size_t i = 0; i < (*it)->stl_triangles.size(); i += 3) {
261  SPoint2 &p1((*it)->stl_vertices_uv[(*it)->stl_triangles[i]]);
262  SPoint2 &p2((*it)->stl_vertices_uv[(*it)->stl_triangles[i + 1]]);
263  SPoint2 &p3((*it)->stl_vertices_uv[(*it)->stl_triangles[i + 2]]);
264  GPoint gp1 = (*it)->point(p1);
265  GPoint gp2 = (*it)->point(p2);
266  GPoint gp3 = (*it)->point(p3);
267  double x[3] = {gp1.x(), gp2.x(), gp3.x()};
268  double y[3] = {gp1.y(), gp2.y(), gp3.y()};
269  double z[3] = {gp1.z(), gp2.z(), gp3.z()};
270  double n[3];
271  normal3points(x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2], n);
272  if(!binary) {
273  fprintf(fp, "facet normal %g %g %g\n", n[0], n[1], n[2]);
274  fprintf(fp, " outer loop\n");
275  for(int j = 0; j < 3; j++)
276  fprintf(fp, " vertex %.16g %.16g %.16g\n", x[j] * scalingFactor,
277  y[j] * scalingFactor, z[j] * scalingFactor);
278  fprintf(fp, " endloop\n");
279  fprintf(fp, "endfacet\n");
280  }
281  else {
282  char data[50];
283  float *coords = (float *)data;
284  coords[0] = (float)n[0];
285  coords[1] = (float)n[1];
286  coords[2] = (float)n[2];
287  for(int j = 0; j < 3; j++) {
288  coords[3 + 3 * j] = (float)(x[j] * scalingFactor);
289  coords[3 + 3 * j + 1] = (float)(y[j] * scalingFactor);
290  coords[3 + 3 * j + 2] = (float)(z[j] * scalingFactor);
291  }
292  data[48] = data[49] = 0;
293  fwrite(data, sizeof(char), 50, fp);
294  }
295  }
296  }
297  else {
298  for(std::size_t i = 0; i < (*it)->triangles.size(); i++)
299  (*it)->triangles[i]->writeSTL(fp, binary, scalingFactor);
300  for(std::size_t i = 0; i < (*it)->quadrangles.size(); i++)
301  (*it)->quadrangles[i]->writeSTL(fp, binary, scalingFactor);
302  }
303  }
304 
305  if(!binary) fprintf(fp, "endsolid %s\n", name.c_str());
306 }
307 
308 int GModel::writeSTL(const std::string &name, bool binary, bool saveAll,
309  double scalingFactor, int oneSolidPerSurface)
310 {
311  FILE *fp = Fopen(name.c_str(), binary ? "wb" : "w");
312  if(!fp) {
313  Msg::Error("Unable to open file '%s'", name.c_str());
314  return 0;
315  }
316 
317  if(noPhysicalGroups()) saveAll = true;
318 
319  if(oneSolidPerSurface == 1) { // one solid per surface
320  for(auto it = firstFace(); it != lastFace(); ++it) {
321  if(saveAll || (*it)->physicals.size()) {
322  std::vector<GFace *> faces(1, *it);
323  std::string name = getElementaryName(2, (*it)->tag());
324  if(name.empty()) {
325  std::ostringstream s;
326  s << "Gmsh Surface " << (*it)->tag();
327  name = s.str();
328  }
329  writeSTLfaces(fp, faces, binary, scalingFactor, name);
330  }
331  }
332  }
333  else if(oneSolidPerSurface == 2) { // one solid per physical surface
334  std::map<int, std::vector<GEntity *> > phys;
335  getPhysicalGroups(2, phys);
336  for(auto it = phys.begin(); it != phys.end(); it++) {
337  std::vector<GFace *> faces;
338  for(std::size_t i = 0; i < it->second.size(); i++) {
339  faces.push_back(static_cast<GFace *>(it->second[i]));
340  }
341  std::string name = getPhysicalName(2, it->first);
342  if(name.empty()) {
343  std::ostringstream s;
344  s << "Gmsh Physical Surface " << it->first;
345  name = s.str();
346  }
347  writeSTLfaces(fp, faces, binary, scalingFactor, name);
348  }
349  }
350  else { // one solid
351  std::vector<GFace *> faces;
352  for(auto it = firstFace(); it != lastFace(); ++it) {
353  if(saveAll || (*it)->physicals.size()) { faces.push_back(*it); }
354  }
355  std::string name = "Created by Gmsh";
356  writeSTLfaces(fp, faces, binary, scalingFactor, name);
357  }
358 
359  fclose(fp);
360  return 1;
361 }
MTriangle.h
GPoint::y
double y() const
Definition: GPoint.h:22
GModel::getMaxElementaryNumber
int getMaxElementaryNumber(int dim)
Definition: GModel.cpp:817
GFace
Definition: GFace.h:33
Msg::Info
static void Info(const char *fmt,...)
Definition: GmshMessage.cpp:587
SPoint2
Definition: SPoint2.h:12
OS.h
Msg::Debug
static void Debug(const char *fmt,...)
Definition: GmshMessage.cpp:752
c
static double c(int i, int j, fullMatrix< double > &CA, const std::vector< SPoint3 > &P, const std::vector< SPoint3 > &Q)
Definition: discreteFrechetDistance.cpp:15
MVertex
Definition: MVertex.h:24
Msg::Warning
static void Warning(const char *fmt,...)
Definition: GmshMessage.cpp:543
Msg::Error
static void Error(const char *fmt,...)
Definition: GmshMessage.cpp:482
invalidChar
static bool invalidChar(char c)
Definition: GModelIO_STL.cpp:20
SPoint3
Definition: SPoint3.h:14
SBoundingBox3d::min
SPoint3 min() const
Definition: SBoundingBox3d.h:90
GModel::empty
bool empty() const
Definition: GModel.cpp:311
SVector3
Definition: SVector3.h:16
MLine.h
GPoint
Definition: GPoint.h:13
writeSTLfaces
static void writeSTLfaces(FILE *fp, std::vector< GFace * > &faces, bool binary, double scalingFactor, const std::string &name)
Definition: GModelIO_STL.cpp:233
Fopen
FILE * Fopen(const char *f, const char *mode)
Definition: OS.cpp:273
GPoint::z
double z() const
Definition: GPoint.h:23
GModel::writeSTL
int writeSTL(const std::string &name, bool binary=false, bool saveAll=false, double scalingFactor=1.0, int oneSolidPerSurface=0)
Definition: GModelIO_STL.cpp:308
CTX::instance
static CTX * instance()
Definition: Context.cpp:122
GModel::getPhysicalGroups
void getPhysicalGroups(std::map< int, std::vector< GEntity * > > groups[4]) const
Definition: GModel.cpp:837
GModel::lastFace
fiter lastFace()
Definition: GModel.h:359
MFace
Definition: MFace.h:20
SwapBytes
void SwapBytes(char *array, int size, int n)
Definition: StringUtils.cpp:16
GModel::getPhysicalName
std::string getPhysicalName(int dim, int num) const
Definition: GModel.cpp:961
GModel::readSTL
int readSTL(const std::string &name, double tolerance=1.e-3)
Definition: GModelIO_STL.cpp:22
GModel::setElementaryName
void setElementaryName(int dim, int tag, const std::string &name)
Definition: GModel.h:500
MVertexRTree::insert
MVertex * insert(MVertex *v, bool warnIfExists=false, std::set< MVertex *, MVertexPtrLessThan > *duplicates=nullptr)
Definition: MVertexRTree.h:38
tolerance
#define tolerance
Definition: curvature.cpp:11
swap
void swap(double &a, double &b)
Definition: meshTriangulation.cpp:27
norm
void norm(const double *vec, double *norm)
Definition: gmshLevelset.cpp:202
GModel::vertices
std::set< GVertex *, GEntityPtrLessThan > vertices
Definition: GModel.h:146
GModel::firstFace
fiter firstFace()
Definition: GModel.h:355
GModel::add
bool add(GRegion *r)
Definition: GModel.h:394
GModel::getElementaryName
std::string getElementaryName(int dim, int tag)
Definition: GModel.cpp:1007
GEntity::tag
int tag() const
Definition: GEntity.h:280
discreteFace.h
MVertexRTree.h
MTriangle
Definition: MTriangle.h:26
StringUtils.h
Context.h
MVertexRTree::find
MVertex * find(double x, double y, double z)
Definition: MVertexRTree.h:70
z
const double z
Definition: GaussQuadratureQuad.cpp:56
MQuadrangle.h
GModel::faces
std::set< GFace *, GEntityPtrLessThan > faces
Definition: GModel.h:144
GModel::noPhysicalGroups
bool noPhysicalGroups()
Definition: GModel.cpp:828
normal3points
void normal3points(double x0, double y0, double z0, double x1, double y1, double z1, double x2, double y2, double z2, double n[3])
Definition: Numeric.cpp:76
GModel::mesh
int mesh(int dimension)
Definition: GModel.cpp:1066
MVertexRTree
Definition: MVertexRTree.h:16
GModel.h
GModel::_storeVerticesInEntities
void _storeVerticesInEntities(std::map< int, MVertex * > &vertices)
Definition: GModel.cpp:2496
SBoundingBox3d::max
SPoint3 max() const
Definition: SBoundingBox3d.h:91
GPoint::x
double x() const
Definition: GPoint.h:21
GModel::_associateEntityWithMeshVertices
void _associateEntityWithMeshVertices()
Definition: GModel.cpp:2470
SBoundingBox3d
Definition: SBoundingBox3d.h:21
MVertex::x
double x() const
Definition: MVertex.h:60
discreteFace
Definition: discreteFace.h:18
faces
static int faces[4][3]
Definition: meshGRegionDelaunayInsertion.cpp:165