-
-
Save ochafik/fca4d509b9e8cd314c7fe49153724f2b to your computer and use it in GitHub Desktop.
OpenSCAD STL Export - float conversions w/ double-conversion lib
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Context: https://github.com/openscad/openscad/pull/4643 | |
| /* | |
| * OpenSCAD (www.openscad.org) | |
| * Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and | |
| * Marius Kintel <marius@kintel.net> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * As a special exception, you have permission to link this program | |
| * with the CGAL library and distribute executables, as long as you | |
| * follow the requirements of the GNU GPL in regard to all of the | |
| * software in the executable aside from CGAL. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
| * | |
| */ | |
| #include "export.h" | |
| #include "PolySet.h" | |
| #include "PolySetUtils.h" | |
| #include "Reindexer.h" | |
| #include "double-conversion/double-conversion.h" | |
| #ifdef ENABLE_MANIFOLD | |
| #include "ManifoldGeometry.h" | |
| #endif | |
| #ifdef ENABLE_CGAL | |
| #include "CGAL_Nef_polyhedron.h" | |
| #include "CGALHybridPolyhedron.h" | |
| #include "cgal.h" | |
| #include "cgalutils.h" | |
| namespace { | |
| #define DC_BUFFER_SIZE (128) | |
| #define DC_FLAGS (double_conversion::DoubleToStringConverter::UNIQUE_ZERO | double_conversion::DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN) | |
| #define DC_INF "inf" | |
| #define DC_NAN "nan" | |
| #define DC_EXP 'e' | |
| #define DC_DECIMAL_LOW_EXP (-6) | |
| #define DC_DECIMAL_HIGH_EXP (21) | |
| #define DC_MAX_LEADING_ZEROES (0) | |
| #define DC_MAX_TRAILING_ZEROES (0) | |
| /* WARNING: using values > 8 will significantly slow double to string | |
| * conversion, defeating the purpose of using double-conversion library */ | |
| #define DC_PRECISION_REQUESTED (6) | |
| void trimTrailingZeros(char *ch) { | |
| size_t s = strlen(ch); | |
| while (s && ch[s - 1] == '0') { | |
| s--; | |
| } | |
| ch[s] = '\0'; | |
| } | |
| // std::string | |
| void doubleToString(double v, char **out, size_t out_size) | |
| { | |
| char *buffer = *out; | |
| #if 1 | |
| static double_conversion::DoubleToStringConverter dc( | |
| double_conversion::DoubleToStringConverter::UNIQUE_ZERO, | |
| NULL, | |
| NULL, | |
| 'e', | |
| DC_DECIMAL_LOW_EXP, DC_DECIMAL_HIGH_EXP, | |
| 100, 100 | |
| // DC_FLAGS, DC_INF, DC_NAN, DC_EXP, | |
| // DC_DECIMAL_LOW_EXP, DC_DECIMAL_HIGH_EXP, DC_MAX_LEADING_ZEROES, DC_MAX_TRAILING_ZEROES | |
| ); | |
| double_conversion::StringBuilder builder(buffer, out_size); | |
| dc.ToPrecision(v, DC_PRECISION_REQUESTED, &builder); | |
| builder.Finalize(); | |
| size_t s = strlen(buffer); | |
| // Strip trailing zeros | |
| char c; | |
| while (s > 1 && ((c = buffer[s - 1]) == '0') || c == '.') { | |
| s--; | |
| if (c == '.') break; | |
| } | |
| buffer[s] = '\0'; | |
| *out += s; | |
| #else | |
| snprintf(buffer, out_size, "%g", v); | |
| #endif | |
| } | |
| template <class T> | |
| std::string toString(const T& v) | |
| { | |
| #if 1 | |
| char buffer[DC_BUFFER_SIZE]; | |
| char *out = buffer; | |
| doubleToString(v[0], &out, DC_BUFFER_SIZE);// - (out - buffer)); | |
| *(out++) = ' '; | |
| doubleToString(v[1], &out, DC_BUFFER_SIZE - (out - buffer)); | |
| *(out++) = ' '; | |
| doubleToString(v[2], &out, DC_BUFFER_SIZE - (out - buffer)); | |
| return buffer; | |
| #elif 1 | |
| char output[DC_BUFFER_SIZE]; | |
| snprintf(output, DC_BUFFER_SIZE, "%g %g %g", v[0], v[1], v[2]); | |
| return output; | |
| #else | |
| return STR(v[0], " ", v[1], " ", v[2]); | |
| #endif | |
| } | |
| std::string toString(double v) | |
| { | |
| char buffer[DC_BUFFER_SIZE]; | |
| char *out = buffer; | |
| doubleToString(v, &out, DC_BUFFER_SIZE); | |
| return buffer; | |
| } | |
| Vector3d toVector(const std::array<double, 3>& pt) { | |
| return {pt[0], pt[1], pt[2]}; | |
| } | |
| Vector3f fromString(const std::string& vertexString) | |
| { | |
| auto cstr = vertexString.c_str(); | |
| #if 0 | |
| static double_conversion::StringToDoubleConverter dc( | |
| double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK | | |
| double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES, | |
| NAN, | |
| NAN, | |
| NULL, | |
| NULL | |
| ); | |
| int n; | |
| size_t len = strlen(cstr); | |
| auto v0 = dc.StringToDouble(cstr, len, &n); | |
| cstr += n; | |
| len -= n; | |
| auto v1 = dc.StringToDouble(cstr, len, &n); | |
| cstr += n; | |
| len -= n; | |
| auto v2 = dc.StringToDouble(cstr, len, &n); | |
| return {v0, v1, v2}; | |
| #else | |
| return { | |
| strtof(cstr, (char**)&cstr), | |
| strtof(cstr, (char**)&cstr), | |
| strtof(cstr, (char**)&cstr) | |
| }; | |
| #endif | |
| } | |
| void write_vector(std::ostream& output, const Vector3f& v) { | |
| for (int i = 0; i < 3; ++i) { | |
| static_assert(sizeof(float) == 4, "Need 32 bit float"); | |
| float f = v[i]; | |
| char *fbeg = reinterpret_cast<char *>(&f); | |
| char data[4]; | |
| uint16_t test = 0x0001; | |
| if (*reinterpret_cast<char *>(&test) == 1) { | |
| std::copy(fbeg, fbeg + 4, data); | |
| } else { | |
| std::reverse_copy(fbeg, fbeg + 4, data); | |
| } | |
| output.write(data, 4); | |
| } | |
| } | |
| uint64_t append_stl(const IndexedTriangleMesh& mesh, std::ostream& output, bool binary) | |
| { | |
| uint64_t triangle_count = 0; | |
| // For each vertex, contains its string conversion and the conversion | |
| // from string again. Only built in ascii case, not binary. | |
| std::vector<std::pair<std::string, Vector3f>> stringifiedVertices; | |
| if (!binary) { | |
| stringifiedVertices.reserve(mesh.vertices.size()); | |
| for (const auto & p : mesh.vertices) { | |
| auto s = toString(p); | |
| stringifiedVertices.emplace_back(s, fromString(s)); | |
| } | |
| } | |
| for (const auto &t : mesh.triangles) { | |
| if (binary) { | |
| const Vector3f &p0 = mesh.vertices[t[0]]; | |
| const Vector3f &p1 = mesh.vertices[t[1]]; | |
| const Vector3f &p2 = mesh.vertices[t[2]]; | |
| // Tessellation already eliminated these cases. | |
| assert(p0 != p1 && p0 != p2 && p1 != p2); | |
| auto normal = (p1 - p0).cross(p2 - p0); | |
| if (!normal.isZero()) { | |
| normal.normalize(); | |
| } | |
| write_vector(output, normal); | |
| write_vector(output, p0); | |
| write_vector(output, p1); | |
| write_vector(output, p2); | |
| char attrib[2] = {0, 0}; | |
| output.write(attrib, 2); | |
| } else { | |
| const auto &transformed0 = stringifiedVertices[t[0]]; | |
| const auto &transformed1 = stringifiedVertices[t[1]]; | |
| const auto &transformed2 = stringifiedVertices[t[2]]; | |
| const auto &p0 = transformed0.second; | |
| const auto &p1 = transformed1.second; | |
| const auto &p2 = transformed2.second; | |
| // Tessellation already eliminated these cases. | |
| assert(p0 != p1 && p0 != p2 && p1 != p2); | |
| auto normal = (p1 - p0).cross(p2 - p0); | |
| if (!normal.isZero()) { | |
| normal.normalize(); | |
| } | |
| output << " facet normal "; | |
| output << toString(normal) << "\n"; | |
| output << " outer loop\n"; | |
| output << " vertex " << transformed0.first << "\n"; | |
| output << " vertex " << transformed1.first << "\n"; | |
| output << " vertex " << transformed2.first << "\n"; | |
| output << " endloop\n"; | |
| output << " endfacet\n"; | |
| } | |
| triangle_count++; | |
| } | |
| return triangle_count; | |
| } | |
| uint64_t append_stl(const PolySet& ps, std::ostream& output, bool binary) | |
| { | |
| IndexedTriangleMesh triangulated; | |
| PolySetUtils::tessellate_faces(ps, triangulated); | |
| if (Feature::ExperimentalPredictibleOutput.is_enabled()) { | |
| IndexedTriangleMesh ordered; | |
| Export::ExportMesh exportMesh { triangulated }; | |
| exportMesh.export_indexed(ordered); | |
| return append_stl(ordered, output, binary); | |
| } else { | |
| return append_stl(triangulated, output, binary); | |
| } | |
| } | |
| /*! | |
| Saves the current 3D CGAL Nef polyhedron as STL to the given file. | |
| The file must be open. | |
| */ | |
| uint64_t append_stl(const CGAL_Nef_polyhedron& root_N, std::ostream& output, | |
| bool binary) | |
| { | |
| uint64_t triangle_count = 0; | |
| if (!root_N.p3->is_simple()) { | |
| LOG(message_group::Export_Warning, "Exported object may not be a valid 2-manifold and may need repair"); | |
| } | |
| PolySet ps(3); | |
| if (!CGALUtils::createPolySetFromNefPolyhedron3(*(root_N.p3), ps)) { | |
| triangle_count += append_stl(ps, output, binary); | |
| } else { | |
| LOG(message_group::Export_Error, "Nef->PolySet failed"); | |
| } | |
| return triangle_count; | |
| } | |
| /*! | |
| Saves the current 3D CGAL Nef polyhedron as STL to the given file. | |
| The file must be open. | |
| */ | |
| uint64_t append_stl(const CGALHybridPolyhedron& hybrid, std::ostream& output, | |
| bool binary) | |
| { | |
| uint64_t triangle_count = 0; | |
| if (!hybrid.isManifold()) { | |
| LOG(message_group::Export_Warning, "Exported object may not be a valid 2-manifold and may need repair"); | |
| } | |
| auto ps = hybrid.toPolySet(); | |
| if (ps) { | |
| triangle_count += append_stl(*ps, output, binary); | |
| } else { | |
| LOG(message_group::Export_Error, "Nef->PolySet failed"); | |
| } | |
| return triangle_count; | |
| } | |
| #ifdef ENABLE_MANIFOLD | |
| /*! | |
| Saves the current 3D Manifold geometry as STL to the given file. | |
| The file must be open. | |
| */ | |
| uint64_t append_stl(const ManifoldGeometry& mani, std::ostream& output, | |
| bool binary) | |
| { | |
| uint64_t triangle_count = 0; | |
| if (!mani.isManifold()) { | |
| LOG(message_group::Export_Warning, "Exported object may not be a valid 2-manifold and may need repair"); | |
| } | |
| auto ps = mani.toPolySet(); | |
| if (ps) { | |
| triangle_count += append_stl(*ps, output, binary); | |
| } else { | |
| LOG(message_group::Export_Error, "Manifold->PolySet failed"); | |
| } | |
| return triangle_count; | |
| } | |
| #endif | |
| uint64_t append_stl(const shared_ptr<const Geometry>& geom, std::ostream& output, | |
| bool binary) | |
| { | |
| uint64_t triangle_count = 0; | |
| if (const auto geomlist = dynamic_pointer_cast<const GeometryList>(geom)) { | |
| for (const Geometry::GeometryItem& item : geomlist->getChildren()) { | |
| triangle_count += append_stl(item.second, output, binary); | |
| } | |
| } else if (const auto N = dynamic_pointer_cast<const CGAL_Nef_polyhedron>(geom)) { | |
| triangle_count += append_stl(*N, output, binary); | |
| } else if (const auto ps = dynamic_pointer_cast<const PolySet>(geom)) { | |
| triangle_count += append_stl(*ps, output, binary); | |
| } else if (const auto hybrid = dynamic_pointer_cast<const CGALHybridPolyhedron>(geom)) { | |
| triangle_count += append_stl(*hybrid, output, binary); | |
| #ifdef ENABLE_MANIFOLD | |
| } else if (const auto mani = dynamic_pointer_cast<const ManifoldGeometry>(geom)) { | |
| triangle_count += append_stl(*mani, output, binary); | |
| #endif | |
| } else if (dynamic_pointer_cast<const Polygon2d>(geom)) { //NOLINT(bugprone-branch-clone) | |
| assert(false && "Unsupported file format"); | |
| } else { //NOLINT(bugprone-branch-clone) | |
| assert(false && "Not implemented"); | |
| } | |
| return triangle_count; | |
| } | |
| } // namespace | |
| void export_stl(const shared_ptr<const Geometry>& geom, std::ostream& output, | |
| bool binary) | |
| { | |
| if (binary) { | |
| char header[80] = "OpenSCAD Model\n"; | |
| output.write(header, sizeof(header)); | |
| char tmp_triangle_count[4] = {0, 0, 0, 0}; // We must fill this in below. | |
| output.write(tmp_triangle_count, 4); | |
| } else { | |
| setlocale(LC_NUMERIC, "C"); // Ensure radix is . (not ,) in output | |
| output << "solid OpenSCAD_Model\n"; | |
| } | |
| uint64_t triangle_count = append_stl(geom, output, binary); | |
| if (binary) { | |
| // Fill in triangle count. | |
| output.seekp(80, std::ios_base::beg); | |
| output.put(triangle_count & 0xff); | |
| output.put((triangle_count >> 8) & 0xff); | |
| output.put((triangle_count >> 16) & 0xff); | |
| output.put((triangle_count >> 24) & 0xff); | |
| if (triangle_count > 4294967295) { | |
| LOG(message_group::Export_Error, "Triangle count exceeded 4294967295, so the stl file is not valid"); | |
| } | |
| } else { | |
| output << "endsolid OpenSCAD_Model\n"; | |
| setlocale(LC_NUMERIC, ""); // Set default locale | |
| } | |
| } | |
| #endif // ENABLE_CGAL |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment