1 // Copyright (c) 2018-2020  GeometryFactory Sarl (France).
2 // All rights reserved.
3 //
4 // Licensees holding a valid commercial license may use this file in
5 // accordance with the commercial license agreement provided with the software.
6 //
7 // This file is part of CGAL (www.cgal.org)
8 //
9 // $URL: https://github.com/CGAL/cgal/blob/v5.3/Stream_support/include/CGAL/IO/STL.h $
10 // $Id: STL.h 4e519a3 2021-05-05T13:15:37+02:00 Sébastien Loriot
11 // SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
12 //
13 // Author(s)     : Maxime Gimeno
14 
15 #ifndef CGAL_IO_STL_H
16 #define CGAL_IO_STL_H
17 
18 #include <CGAL/IO/STL/STL_reader.h>
19 #include <CGAL/IO/helpers.h>
20 
21 #include <CGAL/boost/graph/Named_function_parameters.h>
22 #include <CGAL/boost/graph/named_params_helper.h>
23 #include <CGAL/Kernel/global_functions_3.h>
24 
25 #include <boost/range/value_type.hpp>
26 #include <boost/utility/enable_if.hpp>
27 
28 #include <iostream>
29 #include <fstream>
30 #include <string>
31 
32 #ifdef DOXYGEN_RUNNING
33 #define CGAL_BGL_NP_TEMPLATE_PARAMETERS NamedParameters
34 #define CGAL_BGL_NP_CLASS NamedParameters
35 #endif
36 
37 namespace CGAL {
38 
39 namespace IO {
40 
41 ////////////////////////////////////////////////////////////////////////////////////////////////////
42 ////////////////////////////////////////////////////////////////////////////////////////////////////
43 // Read
44 
45 /*!
46  * \ingroup PkgStreamSupportIoFuncsSTL
47  *
48  * \brief reads the content of `is` into `points` and `facets`, using the \ref IOStreamSTL.
49  *
50  * \attention The polygon soup is not cleared, and the data from the stream are appended.
51  *
52  * \attention When reading a binary file, the flag `std::ios::binary` flag must be set during the creation of the `ifstream`.
53  *
54  * \tparam PointRange a model of the concepts `RandomAccessContainer` and `BackInsertionSequence`
55  *                    whose value type is the point type
56  * \tparam TriangleRange a model of the concept `SequenceContainer`
57  *                      whose `value_type` is itself a model of the concept `SequenceContainer`
58  *                      whose `value_type` is an unsigned integer type convertible to `std::size_t`.
59  * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
60  *
61  * \param is the input stream
62  * \param points points of the soup of triangles
63  * \param facets a range of triangles; each triangle uses the indices of the points in `points`.
64  * \param np optional \ref bgl_namedparameters "Named Parameters" described below
65  *
66  * \cgalNamedParamsBegin
67  *   \cgalParamNBegin{verbose}
68  *     \cgalParamDescription{indicates whether output warnings and error messages should be printed or not.}
69  *     \cgalParamType{Boolean}
70  *     \cgalParamDefault{`false`}
71  *   \cgalParamNEnd
72  * \cgalNamedParamsEnd
73  *
74  * \returns `true` if the reading was successful, `false` otherwise.
75  */
76 template <typename PointRange, typename TriangleRange, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
77 bool read_STL(std::istream& is,
78               PointRange& points,
79               TriangleRange& facets,
80               const CGAL_BGL_NP_CLASS& np
81 #ifndef DOXYGEN_RUNNING
82               , typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr
83 #endif
84               )
85 {
86   const bool verbose = parameters::choose_parameter(parameters::get_parameter(np, internal_np::verbose), false);
87   const bool binary = CGAL::parameters::choose_parameter(CGAL::parameters::get_parameter(np, internal_np::use_binary_mode), true);
88 
89   if(!is.good())
90   {
91     if(verbose)
92       std::cerr<<"File doesn't exist."<<std::endl;
93     return false;
94   }
95 
96   int pos = 0;
97   // Ignore all initial whitespace
98   unsigned char c;
99 
100   while(is.read(reinterpret_cast<char*>(&c), sizeof(c)))
101   {
102     if(!isspace(c))
103     {
104       is.unget(); // move back to the first interesting char
105       break;
106     }
107     ++pos;
108   }
109 
110   if(!is.good()) // reached the end
111     return true;
112 
113   // If we have gone beyond 80 characters and have not read anything yet,
114   // then this must be an ASCII file.
115   if(pos > 80)
116   {
117     if(binary)
118       return false;
119     return internal::parse_ASCII_STL(is, points, facets, verbose);
120   }
121 
122   // We are within the first 80 characters, both ASCII and binary are possible
123 
124   // Read the 5 first characters to check if the first word is "solid"
125   std::string s;
126 
127   char word[6];
128   if(is.read(reinterpret_cast<char*>(&word[0]), sizeof(c)) &&
129      is.read(reinterpret_cast<char*>(&word[1]), sizeof(c)) &&
130      is.read(reinterpret_cast<char*>(&word[2]), sizeof(c)) &&
131      is.read(reinterpret_cast<char*>(&word[3]), sizeof(c)) &&
132      is.read(reinterpret_cast<char*>(&word[4]), sizeof(c)) &&
133      is.read(reinterpret_cast<char*>(&word[5]), sizeof(c)))
134   {
135     s = std::string(word, 5);
136     pos += 5;
137   }
138   else
139   {
140     return true; // empty file
141   }
142 
143   // If the first word is not 'solid', the file must be binary
144   if(s != "solid" || (word[5] !='\n' && word[5] !='\r' && word[5] != ' '))
145   {
146     if(internal::parse_binary_STL(is, points, facets, verbose))
147     {
148       return true;
149     }
150     else
151     {
152       // If we failed to read it as a binary, try as ASCII just in case...
153       // The file does not start with 'solid' anyway, so it's fine to reset it.
154       is.clear();
155       is.seekg(0, std::ios::beg);
156       return internal::parse_ASCII_STL(is, points, facets, verbose);
157     }
158 
159   }
160   // Now, we have found the keyword "solid" which is supposed to indicate that the file is ASCII
161   is.clear();
162   is.seekg(0, std::ios::beg); // the parser needs to read all "solid" to work correctly.
163   if(internal::parse_ASCII_STL(is, points, facets, verbose))
164   {
165     // correctly read the input as an ASCII file
166     return true;
167   }
168   else// Failed to read the ASCII file
169   {
170     // It might have actually have been a binary file... ?
171     return internal::parse_binary_STL(is, points, facets, verbose);
172   }
173 }
174 
175 /// \cond SKIP_IN_MANUAL
176 
177 template <typename PointRange, typename TriangleRange>
178 bool read_STL(std::istream& is, PointRange& points, TriangleRange& facets,
179               typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr)
180 {
181   return read_STL(is, points, facets, parameters::all_default());
182 }
183 
184 /// \endcond
185 
186 /*!
187  * \ingroup PkgStreamSupportIoFuncsSTL
188  *
189  * \brief reads the content of a file named `fname` into `points` and `facets`, using the \ref IOStreamSTL.
190  *
191  *  If `use_binary_mode` is `true`, but the reading fails, ASCII reading will be automatically tested.
192  * \attention The polygon soup is not cleared, and the data from the file are appended.
193  *
194  * \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type.
195  * \tparam TriangleRange a model of the concept `SequenceContainer`
196  *                      whose `value_type` is itself a model of the concept `SequenceContainer`
197  *                      whose `value_type` is an unsigned integer type convertible to `std::size_t`.
198  * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
199  *
200  * \param fname the path to the input file
201  * \param points points of the soup of triangles
202  * \param facets a range of triangles; each triangle uses the indices of the points in `points`.
203  * \param np optional \ref bgl_namedparameters "Named Parameters" described below
204  *
205  * \cgalNamedParamsBegin
206  *   \cgalParamNBegin{use_binary_mode}
207  *     \cgalParamDescription{indicates whether data should be read in binary (`true`) or in ASCII (`false`)}
208  *     \cgalParamType{Boolean}
209  *     \cgalParamDefault{`true`}
210  *   \cgalParamNEnd
211  *
212  *   \cgalParamNBegin{verbose}
213  *     \cgalParamDescription{indicates whether output warnings and error messages should be printed or not.}
214  *     \cgalParamType{Boolean}
215  *     \cgalParamDefault{`false`}
216  *   \cgalParamNEnd
217  * \cgalNamedParamsEnd
218  *
219  * \returns `true` if the reading was successful, `false` otherwise.
220  */
221 template <typename PointRange, typename TriangleRange, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
222 bool read_STL(const std::string& fname,
223               PointRange& points,
224               TriangleRange& facets,
225               const CGAL_BGL_NP_CLASS& np
226 #ifndef DOXYGEN_RUNNING
227               , typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr
228 #endif
229               )
230 {
231   using parameters::choose_parameter;
232   using parameters::get_parameter;
233   const bool binary = parameters::choose_parameter(parameters::get_parameter(np, internal_np::use_binary_mode), true);
234   if(binary)
235   {
236     std::ifstream is(fname, std::ios::binary);
237     CGAL::IO::set_mode(is, BINARY);
238     if(read_STL(is, points, facets, np))
239     {
240       return true;
241     }
242     points.clear();
243     facets.clear();
244   }
245   std::ifstream is(fname);
246   CGAL::IO::set_mode(is, CGAL::IO::ASCII);
247   bool v = choose_parameter(get_parameter(np, internal_np::verbose),
248                             false);
249   return read_STL(is, points, facets, CGAL::parameters::verbose(v).use_binary_mode(false));
250 }
251 
252 /// \cond SKIP_IN_MANUAL
253 
254 template <typename PointRange, typename TriangleRange>
255 bool read_STL(const std::string& fname, PointRange& points, TriangleRange& facets,
256               typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr)
257 {
258   return read_STL(fname, points, facets, parameters::all_default());
259 }
260 
261 /// \endcond
262 
263 ////////////////////////////////////////////////////////////////////////////////////////////////////
264 ////////////////////////////////////////////////////////////////////////////////////////////////////
265 // Write
266 
267 /*!
268  * \ingroup PkgStreamSupportIoFuncsSTL
269  *
270  * \brief writes the content of `points` and `facets` in `os`, using the \ref IOStreamSTL.
271  *
272  * \attention When writing a binary file, the flag `std::ios::binary` flag must be set during the creation of the `ofstream`.
273  *
274  * \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type.
275  * \tparam TriangleRange a model of the concept `SequenceContainer`
276  *                      whose `value_type` is itself a model of the concept `SequenceContainer`
277  *                      whose `value_type` is an unsigned integer type convertible to `std::size_t`.
278  * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
279  *
280  * \param os the output stream
281  * \param points points of the soup of triangles
282  * \param facets a range of triangles; each triangle uses the indices of the points in `points`.
283  * \param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
284  *
285  * \cgalNamedParamsBegin
286  *   \cgalParamNBegin{stream_precision}
287  *     \cgalParamDescription{a parameter used to set the precision (i.e. how many digits are generated) of the output stream}
288  *     \cgalParamType{int}
289  *     \cgalParamDefault{`the precision of the stream `os``}
290  *     \cgalParamExtra{This parameter is only meaningful while using ASCII encoding.}
291  *   \cgalParamNEnd
292  * \cgalNamedParamsEnd
293  *
294  * \return `true` if the writing was successful, `false` otherwise.
295  */
296 template <typename PointRange, typename TriangleRange, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
297 bool write_STL(std::ostream& os,
298                const PointRange& points,
299                const TriangleRange& facets,
300                const CGAL_BGL_NP_CLASS& np
301 #ifndef DOXYGEN_RUNNING
302                , typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr
303 #endif
304                )
305 {
306   typedef typename boost::range_value<TriangleRange>::type                  Triangle;
307 
308   using parameters::choose_parameter;
309   using parameters::get_parameter;
310 
311   typedef typename CGAL::GetPointMap<PointRange, CGAL_BGL_NP_CLASS>::type   PointMap;
312   PointMap point_map = choose_parameter<PointMap>(get_parameter(np, internal_np::point_map));
313 
314   typedef typename boost::property_traits<PointMap>::value_type             Point;
315   typedef typename boost::property_traits<PointMap>::reference              Point_ref;
316   typedef typename CGAL::Kernel_traits<Point>::Kernel                       K;
317   typedef typename K::Vector_3                                              Vector_3;
318 
319   if(!os.good())
320     return false;
321 
322   set_stream_precision_from_NP(os, np);
323 
324   if(get_mode(os) == BINARY)
325   {
326     os << "FileType: Binary                                                                ";
327     const boost::uint32_t N32 = static_cast<boost::uint32_t>(facets.size());
328     os.write(reinterpret_cast<const char *>(&N32), sizeof(N32));
329 
330     for(const Triangle& face : facets)
331     {
332       const Point_ref p = get(point_map, points[face[0]]);
333       const Point_ref q = get(point_map, points[face[1]]);
334       const Point_ref r = get(point_map, points[face[2]]);
335 
336       const Vector_3 n = collinear(p,q,r) ? Vector_3(1,0,0) : unit_normal(p,q,r);
337 
338       const float coords[12] = { static_cast<float>(n.x()), static_cast<float>(n.y()), static_cast<float>(n.z()),
339                                  static_cast<float>(p.x()), static_cast<float>(p.y()), static_cast<float>(p.z()),
340                                  static_cast<float>(q.x()), static_cast<float>(q.y()), static_cast<float>(q.z()),
341                                  static_cast<float>(r.x()), static_cast<float>(r.y()), static_cast<float>(r.z()) };
342 
343       for(int i=0; i<12; ++i)
344         os.write(reinterpret_cast<const char *>(&coords[i]), sizeof(coords[i]));
345       os << "  ";
346     }
347   }
348   else
349   {
350     os << "solid\n";
351     for(const Triangle& face : facets)
352     {
353       const Point_ref p = get(point_map, points[face[0]]);
354       const Point_ref q = get(point_map, points[face[1]]);
355       const Point_ref r = get(point_map, points[face[2]]);
356 
357       const Vector_3 n = collinear(p,q,r) ? Vector_3(1,0,0) : unit_normal(p,q,r);
358       os << "facet normal " << n << "\nouter loop\n";
359       os << "vertex " << p << "\n";
360       os << "vertex " << q << "\n";
361       os << "vertex " << r << "\n";
362       os << "endloop\nendfacet\n";
363     }
364     os << "endsolid"<<std::endl;
365   }
366 
367   return !os.fail();
368 }
369 
370 /// \cond SKIP_IN_MANUAL
371 
372 template <typename PointRange, typename TriangleRange>
373 bool write_STL(std::ostream& os, const PointRange& points, const TriangleRange& facets,
374                typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr)
375 {
376   return write_STL(os, points, facets, parameters::all_default());
377 }
378 
379 /// \endcond
380 
381 /*!
382  * \ingroup PkgStreamSupportIoFuncsSTL
383  *
384  * \brief writes the content of `points` and `facets` in a file named `fname`, using the \ref IOStreamSTL.
385  *
386  * \attention The polygon soup is not cleared, and the data from the file are appended.
387  *
388  * \tparam PointRange a model of the concept `RandomAccessContainer` whose value type is the point type.
389  * \tparam TriangleRange a model of the concept `SequenceContainer`
390  *                      whose `value_type` is itself a model of the concept `SequenceContainer`
391  *                      whose `value_type` is an unsigned integer type convertible to `std::size_t`.
392  * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters"
393  *
394  * \param fname the path to the output file
395  * \param points points of the soup of triangles
396  * \param facets a range of triangles; each triangle uses the indices of the points in `points`.
397  * \param np optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below
398  *
399  * \cgalNamedParamsBegin
400  *   \cgalParamNBegin{use_binary_mode}
401  *     \cgalParamDescription{indicates whether data should be written in binary (`true`) or in ASCII (`false`)}
402  *     \cgalParamType{Boolean}
403  *     \cgalParamDefault{`true`}
404  *   \cgalParamNEnd
405  *
406  *   \cgalParamNBegin{stream_precision}
407  *     \cgalParamDescription{a parameter used to set the precision (i.e. how many digits are generated) of the output stream}
408  *     \cgalParamType{int}
409  *     \cgalParamDefault{`6`}
410  *     \cgalParamExtra{This parameter is only meaningful while using ASCII encoding.}
411  *   \cgalParamNEnd
412  * \cgalNamedParamsEnd
413  *
414  * \return `true` if the writing was successful, `false` otherwise.
415  */
416 template <typename PointRange, typename TriangleRange, typename CGAL_BGL_NP_TEMPLATE_PARAMETERS>
417 bool write_STL(const std::string& fname,
418                const PointRange& points,
419                const TriangleRange& facets,
420                const CGAL_BGL_NP_CLASS& np
421 #ifndef DOXYGEN_RUNNING
422                , typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr
423 #endif
424                )
425 {
426   const bool binary = CGAL::parameters::choose_parameter(CGAL::parameters::get_parameter(np, internal_np::use_binary_mode), true);
427   if(binary)
428   {
429     std::ofstream os(fname, std::ios::binary);
430     CGAL::IO::set_mode(os, CGAL::IO::BINARY);
431     return write_STL(os, points, facets, np);
432   }
433   else
434   {
435     std::ofstream os(fname);
436     CGAL::IO::set_mode(os, CGAL::IO::ASCII);
437     return write_STL(os, points, facets, np);
438   }
439 }
440 
441 /// \cond SKIP_IN_MANUAL
442 
443 template <typename PointRange, typename TriangleRange>
444 bool write_STL(const std::string& fname, const PointRange& points, const TriangleRange& facets,
445                typename boost::enable_if<internal::is_Range<TriangleRange> >::type* = nullptr)
446 {
447   return write_STL(fname, points, facets, parameters::all_default());
448 }
449 
450 /// \endcond
451 
452 } // namespace IO
453 
454 } // namespace CGAL
455 
456 #endif // CGAL_IO_STL_H
457