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