Skip to content

Commit

Permalink
Merge pull request #4146 from ochafik/sort-stl
Browse files Browse the repository at this point in the history
Experimental sort-stl feature for stable outputs (fixes #420).
  • Loading branch information
t-paul committed Feb 26, 2022
2 parents fb10c5d + 20bbd51 commit fef5275
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 112 deletions.
37 changes: 27 additions & 10 deletions src/export.cc
Expand Up @@ -138,18 +138,22 @@ namespace Export {

ExportMesh::ExportMesh(const PolySet& ps)
{
std::map<Vertex, int> vertexMap;
std::vector<std::array<int, 3>> triangleIndices;
for (const auto& p : ps.polygons) {
auto pos1 = vertexMap.emplace(std::make_pair<std::array<double, 3>, int>({p[0].x(), p[0].y(), p[0].z()}, vertexMap.size()));
auto pos2 = vertexMap.emplace(std::make_pair<std::array<double, 3>, int>({p[1].x(), p[1].y(), p[1].z()}, vertexMap.size()));
auto pos3 = vertexMap.emplace(std::make_pair<std::array<double, 3>, int>({p[2].x(), p[2].y(), p[2].z()}, vertexMap.size()));
auto pos1 = vertexMap.emplace(std::make_pair<Vertex, int>({p[0].x(), p[0].y(), p[0].z()}, vertexMap.size()));
auto pos2 = vertexMap.emplace(std::make_pair<Vertex, int>({p[1].x(), p[1].y(), p[1].z()}, vertexMap.size()));
auto pos3 = vertexMap.emplace(std::make_pair<Vertex, int>({p[2].x(), p[2].y(), p[2].z()}, vertexMap.size()));
triangleIndices.push_back({pos1.first->second, pos2.first->second, pos3.first->second});
}

int index = 0;
std::map<int, int> indexTranslationMap;
std::vector<size_t> indexTranslationMap(vertexMap.size());
vertices.reserve(vertexMap.size());

size_t index = 0;
for (const auto& e : vertexMap) {
indexTranslationMap.emplace(e.second, index++);
vertices.push_back(e.first);
indexTranslationMap[e.second] = index++;
}

for (const auto& i : triangleIndices) {
Expand All @@ -160,17 +164,17 @@ ExportMesh::ExportMesh(const PolySet& ps)
});
}

bool ExportMesh::foreach_vertex(const std::function<bool(const std::array<double, 3>&)> callback) const
bool ExportMesh::foreach_vertex(const std::function<bool(const Vertex&)> callback) const
{
for (const auto& e : vertexMap) {
if (!callback(e.first)) {
for (const auto& v : vertices) {
if (!callback(v)) {
return false;
}
}
return true;
}

bool ExportMesh::foreach_triangle(const std::function<bool(const std::array<int, 3>&)> callback) const
bool ExportMesh::foreach_indexed_triangle(const std::function<bool(const std::array<int, 3>&)> callback) const
{
for (const auto& t : triangles) {
if (!callback(t.key)) {
Expand All @@ -180,4 +184,17 @@ bool ExportMesh::foreach_triangle(const std::function<bool(const std::array<int,
return true;
}

bool ExportMesh::foreach_triangle(const std::function<bool(const std::array<std::array<double, 3>, 3>&)> callback) const
{
for (const auto& t : triangles) {
auto& v0 = vertices[t.key[0]];
auto& v1 = vertices[t.key[1]];
auto& v2 = vertices[t.key[2]];
if (!callback({ v0, v1, v2 })) {
return false;
}
}
return true;
}

} // namespace Export
9 changes: 6 additions & 3 deletions src/export.h
Expand Up @@ -154,13 +154,16 @@ struct Triangle {
class ExportMesh
{
public:
typedef std::array<double, 3> Vertex;

ExportMesh(const PolySet& ps);

bool foreach_vertex(const std::function<bool(const std::array<double, 3>&)> callback) const;
bool foreach_triangle(const std::function<bool(const std::array<int, 3>&)> callback) const;
bool foreach_vertex(const std::function<bool(const Vertex&)> callback) const;
bool foreach_indexed_triangle(const std::function<bool(const std::array<int, 3>&)> callback) const;
bool foreach_triangle(const std::function<bool(const std::array<Vertex, 3>&)> callback) const;

private:
std::map<std::array<double, 3>, int> vertexMap;
std::vector<Vertex> vertices;
std::vector<Triangle> triangles;
};

Expand Down
4 changes: 2 additions & 2 deletions src/export_3mf.cc
Expand Up @@ -98,7 +98,7 @@ static bool append_polyset(const PolySet& ps, PLib3MFModelMeshObject *& model)
return false;
}

if (!exportMesh.foreach_triangle(triangleFunc)) {
if (!exportMesh.foreach_indexed_triangle(triangleFunc)) {
export_3mf_error("Can't add triangle to 3MF model.", model);
return false;
}
Expand Down Expand Up @@ -261,7 +261,7 @@ static bool append_polyset(const PolySet& ps, Lib3MF::PWrapper& wrapper, Lib3MF:
return false;
}

if (!exportMesh.foreach_triangle(triangleFunc)) {
if (!exportMesh.foreach_indexed_triangle(triangleFunc)) {
export_3mf_error("Can't add triangle to 3MF model.");
return false;
}
Expand Down
20 changes: 18 additions & 2 deletions src/export_stl.cc
Expand Up @@ -42,6 +42,10 @@ std::string toString(const Vector3d& v)
return STR(v[0] << " " << v[1] << " " << v[2]);
}

Vector3d toVector(const std::array<double, 3>& pt) {
return Vector3d(pt[0], pt[1], pt[2]);
}

Vector3d fromString(const std::string& vertexString)
{
Vector3d v;
Expand Down Expand Up @@ -74,8 +78,7 @@ uint64_t append_stl(const PolySet& ps, std::ostream& output, bool binary)
PolySet triangulated(3);
PolysetUtils::tessellate_faces(ps, triangulated);

for (const auto& p : triangulated.polygons) {
assert(p.size() == 3); // STL only allows triangles
auto processTriangle = [&](const std::array<Vector3d, 3>& p) {
triangle_count++;

if (binary) {
Expand Down Expand Up @@ -135,6 +138,19 @@ uint64_t append_stl(const PolySet& ps, std::ostream& output, bool binary)
output << " endfacet\n";
}
}
};

if (Feature::ExperimentalSortStl.is_enabled()) {
Export::ExportMesh exportMesh { triangulated };
exportMesh.foreach_triangle([&](const auto& pts) {
processTriangle({ toVector(pts[0]), toVector(pts[1]), toVector(pts[2]) });
return true;
});
} else {
for (const auto& p : triangulated.polygons) {
assert(p.size() == 3); // STL only allows triangles
processTriangle({ p[0], p[1], p[2] });
}
}

return triangle_count;
Expand Down
1 change: 1 addition & 0 deletions src/feature.cc
Expand Up @@ -46,6 +46,7 @@ const Feature Feature::ExperimentalVxORenderersDirect("vertex-object-renderers-d
const Feature Feature::ExperimentalVxORenderersPrealloc("vertex-object-renderers-prealloc", "Enable preallocating buffers in vertex object renderers");
const Feature Feature::ExperimentalTextMetricsFunctions("textmetrics", "Enable the <code>textmetrics()</code> and <code>fontmetrics()</code> functions.");
const Feature Feature::ExperimentalImportFunction("import-function", "Enable import function returning data instead of geometry.");
const Feature Feature::ExperimentalSortStl("sort-stl", "Sort the STL output for predictible, diffable results.");

Feature::Feature(const std::string& name, const std::string& description)
: enabled(false), name(name), description(description)
Expand Down
1 change: 1 addition & 0 deletions src/feature.h
Expand Up @@ -28,6 +28,7 @@ class Feature
static const Feature ExperimentalVxORenderersPrealloc;
static const Feature ExperimentalTextMetricsFunctions;
static const Feature ExperimentalImportFunction;
static const Feature ExperimentalSortStl;

const std::string& get_name() const;
const std::string& get_description() const;
Expand Down
3 changes: 1 addition & 2 deletions tests/CMakeLists.txt
Expand Up @@ -1166,8 +1166,7 @@ add_cmdline_test(pdfexporttest SCRIPT ${EXPORT_PNGTEST_PY} SUFFIX png FILES ${SC

add_cmdline_test(monotonepngtest OPENSCAD SUFFIX png FILES ${EXPORT3D_CGAL_TEST_FILES} ${EXPORT3D_CGALCGAL_TEST_FILES} ARGS --colorscheme=Monotone --render)

# Disabled for now, needs implementation of #420 to be stable
#add_cmdline_test(stlexport ARGS SUFFIX stl FILES ${EXPORT_STL_TEST_FILES})
add_cmdline_test(stlexport OPENSCAD SUFFIX stl FILES ${EXPORT_STL_TEST_FILES} ARGS --enable=sort-stl --render)
add_cmdline_test(3mfexport OPENSCAD ARGS SUFFIX 3mf FILES ${EXPORT_3MF_TEST_FILES})

# stlpngtest: direct STL output, preview rendering
Expand Down

0 comments on commit fef5275

Please sign in to comment.