From f484953a5a1fecd878242ca8d2f5175151b81678 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 20 Sep 2021 14:40:56 +0200 Subject: [PATCH 01/25] Fix of Print setting changes not applied to all copies if one copy rotated #6971 --- src/libslic3r/PrintApply.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 10960c5356..da26f905cb 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1300,8 +1300,10 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ num_extruders, painting_extruders, *print_object_regions, - [&print_object, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { - update_apply_status(print_object.invalidate_state_by_config_options(old_config, new_config, diff_keys)); + [&print_object, it_print_object, it_print_object_end, &update_apply_status](const PrintRegionConfig &old_config, const PrintRegionConfig &new_config, const t_config_option_keys &diff_keys) { + for (auto it = it_print_object; it != it_print_object_end; ++it) + if ((*it)->m_shared_regions != nullptr) + update_apply_status((*it)->invalidate_state_by_config_options(old_config, new_config, diff_keys)); })) { // Regions are valid, just keep them. } else { From 8a2a9dba2f8f94da0106b60df613cd04ada4d595 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 20 Sep 2021 17:12:22 +0200 Subject: [PATCH 02/25] Eradicated admesh from TriangleMesh: TriangleMesh newly only holds indexed_triangle_set and TriangleMeshStats. TriangleMeshStats contains an excerpt of stl_stats. TriangleMeshStats are updated when initializing with indexed_triangle_set. Admesh triangle mesh fixing is newly only used when loading an STL. AMF / 3MF / OBJ file formats are already indexed triangle sets, thus they are no more converted to admesh stl_file format, nor fixed through admesh repair machinery. When importing AMF / 3MF / OBJ files, volume is calculated and if negative, all faces are flipped. Also a bounding box and number of open edges is calculated. Implemented its_number_of_patches(), its_num_open_edges() Optimized its_split(), its_is_splittable() using a visitor pattern. Reworked QHull integration into TriangleMesh: 1) Face normals were not right. 2) Indexed triangle set is newly emitted instead of duplicating vertices for each face. Fixed cut_mesh(): Orient the triangulated faces correctly. --- lib/Slic3r/Test.pm | 1 - sandboxes/aabb-evaluation/aabb-evaluation.cpp | 3 +- sandboxes/meshboolean/MeshBoolean.cpp | 1 - sandboxes/opencsg/Engine.cpp | 3 - sandboxes/opencsg/ShaderCSGDisplay.cpp | 9 +- src/PrusaSlicer.cpp | 2 +- src/admesh/connect.cpp | 68 +- src/admesh/stl.h | 27 +- src/admesh/stl_io.cpp | 11 +- src/libslic3r/Format/3mf.cpp | 95 +- src/libslic3r/Format/AMF.cpp | 60 +- src/libslic3r/Format/OBJ.cpp | 126 ++- src/libslic3r/Format/PRUS.cpp | 333 ------- src/libslic3r/Format/PRUS.hpp | 11 - src/libslic3r/Format/STL.cpp | 3 +- src/libslic3r/GCode/GCodeProcessor.cpp | 24 +- src/libslic3r/MeshBoolean.cpp | 36 +- src/libslic3r/MeshSplitImpl.hpp | 155 ++-- src/libslic3r/Model.cpp | 115 +-- src/libslic3r/Model.hpp | 10 +- src/libslic3r/SLA/Hollowing.cpp | 6 +- src/libslic3r/SLA/ReprojectPointsOnMesh.hpp | 1 - src/libslic3r/SLA/Rotfinder.cpp | 3 - src/libslic3r/SLAPrint.cpp | 1 - src/libslic3r/SLAPrint.hpp | 1 - src/libslic3r/SLAPrintSteps.cpp | 1 - src/libslic3r/SimplifyMesh.hpp | 4 +- src/libslic3r/Technologies.hpp | 4 +- src/libslic3r/TriangleMesh.cpp | 871 +++++++++--------- src/libslic3r/TriangleMesh.hpp | 154 +++- src/libslic3r/TriangleMeshSlicer.cpp | 36 +- src/libslic3r/TriangleMeshSlicer.hpp | 16 +- src/libslic3r/utils.cpp | 1 + src/slic3r/GUI/3DScene.cpp | 29 +- src/slic3r/GUI/GLModel.cpp | 1 - src/slic3r/GUI/GUI_ObjectList.cpp | 24 +- src/slic3r/GUI/Gizmos/GLGizmoCut.cpp | 4 +- src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp | 5 +- src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp | 7 +- src/slic3r/GUI/MainFrame.cpp | 1 - src/slic3r/GUI/MeshUtils.cpp | 2 - src/slic3r/GUI/Plater.cpp | 13 +- src/slic3r/Utils/FixModelByWin10.cpp | 1 + tests/fff_print/test_data.cpp | 61 +- tests/fff_print/test_trianglemesh.cpp | 136 +-- tests/libslic3r/test_3mf.cpp | 3 - tests/libslic3r/test_aabbindirect.cpp | 1 - tests/libslic3r/test_hollowing.cpp | 1 - tests/libslic3r/test_marchingsquares.cpp | 1 - tests/sla_print/sla_print_tests.cpp | 1 - tests/sla_print/sla_raycast_tests.cpp | 1 - tests/sla_print/sla_supptgen_tests.cpp | 5 - tests/sla_print/sla_test_utils.cpp | 60 +- tests/sla_print/sla_test_utils.hpp | 5 - xs/t/01_trianglemesh.t | 110 +-- xs/xsp/Model.xsp | 10 - xs/xsp/TriangleMesh.xsp | 138 +-- xs/xsp/my.map | 1 - xs/xsp/typemap.xspt | 1 - 59 files changed, 1056 insertions(+), 1758 deletions(-) delete mode 100644 src/libslic3r/Format/PRUS.cpp delete mode 100644 src/libslic3r/Format/PRUS.hpp diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 8d2a79f2a4..d0696aab02 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -139,7 +139,6 @@ sub mesh { my $mesh = Slic3r::TriangleMesh->new; $mesh->ReadFromPerl($vertices, $facets); - $mesh->repair; $mesh->scale_xyz(Slic3r::Pointf3->new(@{$params{scale_xyz}})) if $params{scale_xyz}; $mesh->translate(@{$params{translate}}) if $params{translate}; return $mesh; diff --git a/sandboxes/aabb-evaluation/aabb-evaluation.cpp b/sandboxes/aabb-evaluation/aabb-evaluation.cpp index 9ec7451e50..1019ecf28b 100644 --- a/sandboxes/aabb-evaluation/aabb-evaluation.cpp +++ b/sandboxes/aabb-evaluation/aabb-evaluation.cpp @@ -212,8 +212,7 @@ int main(const int argc, const char *argv[]) return -1; } - mesh.repair(); - if (mesh.facets_count() == 0) { + if (mesh.empty()) { std::cerr << "Error loading " << argv[1] << " . It is empty." << std::endl; return -1; } diff --git a/sandboxes/meshboolean/MeshBoolean.cpp b/sandboxes/meshboolean/MeshBoolean.cpp index 392d907074..c8649888fd 100644 --- a/sandboxes/meshboolean/MeshBoolean.cpp +++ b/sandboxes/meshboolean/MeshBoolean.cpp @@ -24,7 +24,6 @@ int main(const int argc, const char * argv[]) TriangleMesh input; input.ReadSTLFile(argv[1]); - input.repair(); Benchmark bench; diff --git a/sandboxes/opencsg/Engine.cpp b/sandboxes/opencsg/Engine.cpp index 53e3402948..d8f1d34647 100644 --- a/sandboxes/opencsg/Engine.cpp +++ b/sandboxes/opencsg/Engine.cpp @@ -409,7 +409,6 @@ void CSGDisplay::on_scene_updated(const Scene &scene) interior.transform(po->trafo().inverse()); mshinst.merge(interior); - mshinst.require_shared_vertices(); mi->transform_mesh(&mshinst); @@ -417,14 +416,12 @@ void CSGDisplay::on_scene_updated(const Scene &scene) auto center = bb.center().cast(); mshinst.translate(-center); - mshinst.require_shared_vertices(); m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection, m_csgsettings.get_convexity()); } for (const sla::DrainHole &holept : holedata) { TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh()); - holemesh.require_shared_vertices(); m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1); } } diff --git a/sandboxes/opencsg/ShaderCSGDisplay.cpp b/sandboxes/opencsg/ShaderCSGDisplay.cpp index 8ceb234be0..2413bad5bb 100644 --- a/sandboxes/opencsg/ShaderCSGDisplay.cpp +++ b/sandboxes/opencsg/ShaderCSGDisplay.cpp @@ -43,7 +43,6 @@ void ShaderCSGDisplay::on_scene_updated(const Scene &scene) interior.transform(po->trafo().inverse()); mshinst.merge(interior); - mshinst.require_shared_vertices(); mi->transform_mesh(&mshinst); @@ -51,15 +50,11 @@ void ShaderCSGDisplay::on_scene_updated(const Scene &scene) auto center = bb.center().cast(); mshinst.translate(-center); - mshinst.require_shared_vertices(); add_mesh(mshinst); } - for (const sla::DrainHole &holept : holedata) { - TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh()); - holemesh.require_shared_vertices(); - add_mesh(holemesh); - } + for (const sla::DrainHole &holept : holedata) + add_mesh(sla::to_triangle_mesh(holept.to_mesh())); } repaint(); diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 3490b81836..0da5e73808 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -397,7 +397,7 @@ int CLI::run(int argc, char **argv) TriangleMesh mesh = model.mesh(); mesh.repair(); - TriangleMeshPtrs meshes = mesh.cut_by_grid(m_config.option("cut_grid")->value); + std::vector meshes = mesh.cut_by_grid(m_config.option("cut_grid")->value); size_t i = 0; for (TriangleMesh* m : meshes) { Model out; diff --git a/src/admesh/connect.cpp b/src/admesh/connect.cpp index e5491b1aae..8c3ab154ad 100644 --- a/src/admesh/connect.cpp +++ b/src/admesh/connect.cpp @@ -239,6 +239,7 @@ private: return edge_a.facet_number != edge_b.facet_number && edge_a == edge_b; } + // Connect edge_a with edge_b, update edge connection statistics. static void record_neighbors(stl_file *stl, const HashEdge &edge_a, const HashEdge &edge_b) { // Facet a's neighbor is facet b @@ -249,7 +250,7 @@ private: stl->neighbors_start[edge_b.facet_number].neighbor[edge_b.which_edge % 3] = edge_a.facet_number; /* sets the .neighbor part */ stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] = (edge_a.which_edge + 2) % 3; /* sets the .which_vertex_not part */ - if (((edge_a.which_edge < 3) && (edge_b.which_edge < 3)) || ((edge_a.which_edge > 2) && (edge_b.which_edge > 2))) { + if ((edge_a.which_edge < 3 && edge_b.which_edge < 3) || (edge_a.which_edge > 2 && edge_b.which_edge > 2)) { // These facets are oriented in opposite directions, their normals are probably messed up. stl->neighbors_start[edge_a.facet_number].which_vertex_not[edge_a.which_edge % 3] += 3; stl->neighbors_start[edge_b.facet_number].which_vertex_not[edge_b.which_edge % 3] += 3; @@ -479,12 +480,13 @@ void stl_check_facets_exact(stl_file *stl) void stl_check_facets_nearby(stl_file *stl, float tolerance) { - if ( (stl->stats.connected_facets_1_edge == stl->stats.number_of_facets) - && (stl->stats.connected_facets_2_edge == stl->stats.number_of_facets) - && (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets)) { + assert(stl->stats.connected_facets_3_edge <= stl->stats.connected_facets_2_edge); + assert(stl->stats.connected_facets_2_edge <= stl->stats.connected_facets_1_edge); + assert(stl->stats.connected_facets_1_edge <= stl->stats.number_of_facets); + + if (stl->stats.connected_facets_3_edge == stl->stats.number_of_facets) // No need to check any further. All facets are connected. return; - } HashTableEdges hash_table(stl->stats.number_of_facets); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { @@ -514,22 +516,12 @@ void stl_remove_unconnected_facets(stl_file *stl) /* Update list of connected edges */ stl_neighbors &neighbors = stl->neighbors_start[facet_number]; // Update statistics on unconnected triangle edges. - switch ((neighbors.neighbor[0] == -1) + (neighbors.neighbor[1] == -1) + (neighbors.neighbor[2] == -1)) { - case 0: // Facet has 3 neighbors - -- stl->stats.connected_facets_3_edge; - -- stl->stats.connected_facets_2_edge; - -- stl->stats.connected_facets_1_edge; - break; - case 1: // Facet has 2 neighbors - -- stl->stats.connected_facets_2_edge; - -- stl->stats.connected_facets_1_edge; - break; - case 2: // Facet has 1 neighbor - -- stl->stats.connected_facets_1_edge; - case 3: // Facet has 0 neighbors - break; - default: - assert(false); + switch (neighbors.num_neighbors()) { + case 3: -- stl->stats.connected_facets_3_edge; // fall through + case 2: -- stl->stats.connected_facets_2_edge; // fall through + case 1: -- stl->stats.connected_facets_1_edge; // fall through + case 0: break; + default: assert(false); } if (facet_number < int(-- stl->stats.number_of_facets)) { @@ -555,20 +547,14 @@ void stl_remove_unconnected_facets(stl_file *stl) auto remove_degenerate = [stl, remove_facet](int facet) { - // Update statistics on face connectivity. - auto stl_update_connects_remove_1 = [stl](int facet_num) { - //FIXME when decreasing 3_edge, should I increase 2_edge etc? - switch ((stl->neighbors_start[facet_num].neighbor[0] == -1) + (stl->neighbors_start[facet_num].neighbor[1] == -1) + (stl->neighbors_start[facet_num].neighbor[2] == -1)) { - case 0: // Facet has 3 neighbors - -- stl->stats.connected_facets_3_edge; break; - case 1: // Facet has 2 neighbors - -- stl->stats.connected_facets_2_edge; break; - case 2: // Facet has 1 neighbor - -- stl->stats.connected_facets_1_edge; break; - case 3: // Facet has 0 neighbors - break; - default: - assert(false); + // Update statistics on face connectivity after one edge was disconnected on the facet "facet_num". + auto update_connects_remove_1 = [stl](int facet_num) { + switch (stl->neighbors_start[facet_num].num_neighbors()) { + case 0: assert(false); break; + case 1: -- stl->stats.connected_facets_1_edge; break; + case 2: -- stl->stats.connected_facets_2_edge; break; + case 3: -- stl->stats.connected_facets_3_edge; break; + default: assert(false); } }; @@ -604,9 +590,9 @@ void stl_remove_unconnected_facets(stl_file *stl) // Update statistics on edge connectivity. if ((neighbor[0] == -1) && (neighbor[1] != -1)) - stl_update_connects_remove_1(neighbor[1]); + update_connects_remove_1(neighbor[1]); if ((neighbor[1] == -1) && (neighbor[0] != -1)) - stl_update_connects_remove_1(neighbor[0]); + update_connects_remove_1(neighbor[0]); if (neighbor[0] >= 0) { if (neighbor[1] >= 0) { @@ -634,7 +620,7 @@ void stl_remove_unconnected_facets(stl_file *stl) stl->neighbors_start[neighbor[1]].which_vertex_not[(vnot[1] + 1) % 3] = vnot[0]; } if (neighbor[2] >= 0) { - stl_update_connects_remove_1(neighbor[2]); + update_connects_remove_1(neighbor[2]); stl->neighbors_start[neighbor[2]].neighbor[(vnot[2] + 1) % 3] = -1; } @@ -652,11 +638,9 @@ void stl_remove_unconnected_facets(stl_file *stl) ++ i; if (stl->stats.connected_facets_1_edge < (int)stl->stats.number_of_facets) { - // remove completely unconnected facets + // There are some faces with no connected edge at all. Remove completely unconnected facets. for (uint32_t i = 0; i < stl->stats.number_of_facets;) - if (stl->neighbors_start[i].neighbor[0] == -1 && - stl->neighbors_start[i].neighbor[1] == -1 && - stl->neighbors_start[i].neighbor[2] == -1) { + if (stl->neighbors_start[i].num_neighbors() == 0) { // This facet is completely unconnected. Remove it. remove_facet(i); assert(stl_validate(stl)); diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 33e2b9c946..8c30a6ae5d 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -79,8 +79,7 @@ struct stl_neighbors { which_vertex_not[1] = -1; which_vertex_not[2] = -1; } - int num_neighbors_missing() const { return (this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1); } - int num_neighbors() const { return 3 - this->num_neighbors_missing(); } + int num_neighbors() const { return 3 - ((this->neighbor[0] == -1) + (this->neighbor[1] == -1) + (this->neighbor[2] == -1)); } // Index of a neighbor facet. int neighbor[3]; @@ -92,28 +91,44 @@ struct stl_stats { stl_stats() { memset(&header, 0, 81); } char header[81]; stl_type type = (stl_type)0; + // Should always match the number of facets stored inside stl_file::facet_start. uint32_t number_of_facets = 0; + // Bounding box. stl_vertex max = stl_vertex::Zero(); stl_vertex min = stl_vertex::Zero(); stl_vertex size = stl_vertex::Zero(); float bounding_diameter = 0.f; float shortest_edge = 0.f; + // After repair, the volume shall always be positive. float volume = -1.f; + // Number of face edges connected to another face. + // Don't use this statistics after repair, use the connected_facets_1/2/3_edge instead! int connected_edges = 0; + // Faces with >=1, >=2 and 3 edges connected to another face. int connected_facets_1_edge = 0; int connected_facets_2_edge = 0; int connected_facets_3_edge = 0; + // Faces with 1, 2 and 3 open edges after exact chaining, but before repair. int facets_w_1_bad_edge = 0; int facets_w_2_bad_edge = 0; int facets_w_3_bad_edge = 0; + // Number of faces read form an STL file. int original_num_facets = 0; + // Number of edges connected one to another by snapping their end vertices. int edges_fixed = 0; + // Number of faces removed because they were degenerated. int degenerate_facets = 0; + // Total number of facets removed: Degenerate faces and unconnected faces. int facets_removed = 0; + // Number of faces added by hole filling. int facets_added = 0; + // Number of faces reversed because of negative volume or because one patch was connected to another patch with incompatible normals. int facets_reversed = 0; + // Number of incompatible edges remaining after the patches were connected together and possibly their normals flipped. int backwards_edges = 0; + // Number of triangles, which were flipped during the fixing process. int normals_fixed = 0; + // Number of connected triangle patches. int number_of_parts = 0; void clear() { *this = stl_stats(); } @@ -135,13 +150,11 @@ struct stl_file { std::vector facet_start; std::vector neighbors_start; // Statistics - stl_stats stats; + stl_stats stats; }; struct indexed_triangle_set { - indexed_triangle_set() {} - void clear() { indices.clear(); vertices.clear(); } size_t memsize() const { @@ -149,9 +162,7 @@ struct indexed_triangle_set } std::vector indices; - std::vector vertices; - //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. - //std::vector normals + std::vector vertices; bool empty() const { return indices.empty() || vertices.empty(); } }; diff --git a/src/admesh/stl_io.cpp b/src/admesh/stl_io.cpp index ddf377c781..26f5dc3212 100644 --- a/src/admesh/stl_io.cpp +++ b/src/admesh/stl_io.cpp @@ -205,11 +205,12 @@ bool stl_write_quad_object(stl_file *stl, char *file) fprintf(fp, "CQUAD\n"); for (uint32_t i = 0; i < stl->stats.number_of_facets; ++ i) { - switch (stl->neighbors_start[i].num_neighbors_missing()) { - case 0: color = connect_color; break; - case 1: color = uncon_1_color; break; - case 2: color = uncon_2_color; break; - default: color = uncon_3_color; + switch (stl->neighbors_start[i].num_neighbors()) { + case 0: + default: color = uncon_3_color; break; + case 1: color = uncon_2_color; break; + case 2: color = uncon_1_color; break; + case 3: color = connect_color; break; } fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[0](0), stl->facet_start[i].vertex[0](1), stl->facet_start[i].vertex[0](2), color(0), color(1), color(2)); fprintf(fp, "%f %f %f %1.1f %1.1f %1.1f 1\n", stl->facet_start[i].vertex[1](0), stl->facet_start[i].vertex[1](1), stl->facet_start[i].vertex[1](2), color(0), color(1), color(2)); diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 043f951efb..90fa9bfaed 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -305,8 +305,8 @@ namespace Slic3r { struct Geometry { - std::vector vertices; - std::vector triangles; + std::vector vertices; + std::vector triangles; std::vector custom_supports; std::vector custom_seam; std::vector mmu_segmentation; @@ -720,7 +720,7 @@ namespace Slic3r { } // use the geometry to create the volumes in the new model objects - ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() / 3 - 1 }); + ObjectMetadata::VolumeMetadataList volumes(1, { 0, (unsigned int)geometry->triangles.size() - 1 }); // for each instance after the 1st, create a new model object containing only that instance // and copy into it the geometry @@ -793,7 +793,7 @@ namespace Slic3r { // config data not found, this model was not saved using slic3r pe // add the entire geometry as the single volume to generate - volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() / 3 - 1); + volumes.emplace_back(0, (int)obj_geometry->second.triangles.size() - 1); // select as volumes volumes_ptr = &volumes; @@ -1559,9 +1559,10 @@ namespace Slic3r { { // appends the vertex coordinates // missing values are set equal to ZERO - m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR)); - m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR)); - m_curr_object.geometry.vertices.push_back(m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR)); + m_curr_object.geometry.vertices.emplace_back( + m_unit_factor * get_attribute_value_float(attributes, num_attributes, X_ATTR), + m_unit_factor * get_attribute_value_float(attributes, num_attributes, Y_ATTR), + m_unit_factor * get_attribute_value_float(attributes, num_attributes, Z_ATTR)); return true; } @@ -1595,9 +1596,10 @@ namespace Slic3r { // appends the triangle's vertices indices // missing values are set equal to ZERO - m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR)); - m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR)); - m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + m_curr_object.geometry.triangles.emplace_back( + get_attribute_value_int(attributes, num_attributes, V1_ATTR), + get_attribute_value_int(attributes, num_attributes, V2_ATTR), + get_attribute_value_int(attributes, num_attributes, V3_ATTR)); m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); @@ -1886,7 +1888,7 @@ namespace Slic3r { return false; } - unsigned int geo_tri_count = (unsigned int)geometry.triangles.size() / 3; + unsigned int geo_tri_count = (unsigned int)geometry.triangles.size(); unsigned int renamed_volumes_count = 0; for (const ObjectMetadata::VolumeMetadata& volume_data : volumes) { @@ -1897,77 +1899,50 @@ namespace Slic3r { Transform3d volume_matrix_to_object = Transform3d::Identity(); bool has_transform = false; -#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT - bool is_left_handed = false; -#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT // extract the volume transformation from the volume's metadata, if present for (const Metadata& metadata : volume_data.metadata) { if (metadata.key == MATRIX_KEY) { volume_matrix_to_object = Slic3r::Geometry::transform3d_from_string(metadata.value); has_transform = ! volume_matrix_to_object.isApprox(Transform3d::Identity(), 1e-10); -#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT - is_left_handed = Slic3r::Geometry::Transformation(volume_matrix_to_object).is_left_handed(); -#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT break; } } // splits volume out of imported geometry - TriangleMesh triangle_mesh; - stl_file &stl = triangle_mesh.stl; - unsigned int triangles_count = volume_data.last_triangle_id - volume_data.first_triangle_id + 1; - stl.stats.type = inmemory; - stl.stats.number_of_facets = (uint32_t)triangles_count; - stl.stats.original_num_facets = (int)stl.stats.number_of_facets; - stl_allocate(&stl); - - unsigned int src_start_id = volume_data.first_triangle_id * 3; - - for (unsigned int i = 0; i < triangles_count; ++i) { - unsigned int ii = i * 3; - stl_facet& facet = stl.facet_start[i]; - for (unsigned int v = 0; v < 3; ++v) { - unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3; - if (tri_id + 2 >= geometry.vertices.size()) { - add_error("Malformed triangle mesh"); + std::vector faces(geometry.triangles.begin() + volume_data.first_triangle_id, geometry.triangles.begin() + volume_data.last_triangle_id + 1); + const size_t triangles_count = faces.size(); + for (Vec3i face : faces) + for (unsigned int tri_id : face) + if (tri_id < 0 || tri_id >= geometry.vertices.size()) { + add_error("Found invalid vertex id"); return false; } - facet.vertex[v] = Vec3f(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]); - } - } - - stl_get_size(&stl); - triangle_mesh.repair(); - -#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT - // PrusaSlicer older than 2.4.0 saved mirrored volumes with reversed winding of the triangles - // This caused the call to TriangleMesh::repair() to reverse all the facets because the calculated volume was negative - if (is_left_handed && stl.stats.facets_reversed > 0 && stl.stats.facets_reversed == stl.stats.original_num_facets) - stl.stats.facets_reversed = 0; -#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT + TriangleMesh triangle_mesh(std::move(geometry.vertices), std::move(faces)); if (m_version == 0) { // if the 3mf was not produced by PrusaSlicer and there is only one instance, // bake the transformation into the geometry to allow the reload from disk command // to work properly if (object.instances.size() == 1) { - triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix()); + triangle_mesh.transform(object.instances.front()->get_transformation().get_matrix(), false); object.instances.front()->set_transformation(Slic3r::Geometry::Transformation()); + //FIXME do the mesh fixing? } } + if (triangle_mesh.volume() < 0) + triangle_mesh.flip_triangles(); ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); // stores the volume matrix taken from the metadata, if present if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); - volume->calculate_convex_hull(); // recreate custom supports, seam and mmu segmentation from previously loaded attribute volume->supported_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count); volume->mmu_segmentation_facets.reserve(triangles_count); - for (unsigned i=0; imesh().repaired) - throw Slic3r::FileIOError("store_3mf() requires repair()"); - if (!volume->mesh().has_shared_vertices()) - throw Slic3r::FileIOError("store_3mf() requires shared vertices"); - volumes_offsets.insert({ volume, Offsets(vertices_count) }); const indexed_triangle_set &its = volume->mesh().its; @@ -2588,10 +2558,7 @@ namespace Slic3r { if (volume == nullptr) continue; -#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT bool is_left_handed = volume->is_left_handed(); -#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT - VolumeToOffsetsMap::iterator volume_it = volumes_offsets.find(volume); assert(volume_it != volumes_offsets.end()); @@ -2606,7 +2573,6 @@ namespace Slic3r { { const Vec3i &idx = its.indices[i]; char *ptr = buf; -#if ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << " v1=\"" << boost::spirit::int_ << "\" v2=\"" << boost::spirit::int_ << @@ -2614,15 +2580,6 @@ namespace Slic3r { idx[is_left_handed ? 2 : 0] + volume_it->second.first_vertex_id, idx[1] + volume_it->second.first_vertex_id, idx[is_left_handed ? 0 : 2] + volume_it->second.first_vertex_id); -#else - boost::spirit::karma::generate(ptr, boost::spirit::lit(" <") << TRIANGLE_TAG << - " v1=\"" << boost::spirit::int_ << - "\" v2=\"" << boost::spirit::int_ << - "\" v3=\"" << boost::spirit::int_ << "\"", - idx[0] + volume_it->second.first_vertex_id, - idx[1] + volume_it->second.first_vertex_id, - idx[2] + volume_it->second.first_vertex_id); -#endif // ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT *ptr = '\0'; output_buffer += buf; } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 35b3e0cf43..9392485f62 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -244,11 +244,11 @@ struct AMFParserContext // Map from obect name to object idx & instances. std::map m_object_instances_map; // Vertices parsed for the current m_object. - std::vector m_object_vertices; + std::vector m_object_vertices; // Current volume allocated for an amf/object/mesh/volume subtree. ModelVolume *m_volume { nullptr }; // Faces collected for the current m_volume. - std::vector m_volume_facets; + std::vector m_volume_facets; // Transformation matrix of a volume mesh from its coordinate system to Object's coordinate system. Transform3d m_volume_transform; // Current material allocated for an amf/metadata subtree. @@ -598,9 +598,7 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VERTEX: assert(m_object); // Parse the vertex data - m_object_vertices.emplace_back((float)atof(m_value[0].c_str())); - m_object_vertices.emplace_back((float)atof(m_value[1].c_str())); - m_object_vertices.emplace_back((float)atof(m_value[2].c_str())); + m_object_vertices.emplace_back(float(atof(m_value[0].c_str())), float(atof(m_value[1].c_str())), float(atof(m_value[1].c_str()))); m_value[0].clear(); m_value[1].clear(); m_value[2].clear(); @@ -609,9 +607,7 @@ void AMFParserContext::endElement(const char * /* name */) // Faces of the current volume: case NODE_TYPE_TRIANGLE: assert(m_object && m_volume); - m_volume_facets.emplace_back(atoi(m_value[0].c_str())); - m_volume_facets.emplace_back(atoi(m_value[1].c_str())); - m_volume_facets.emplace_back(atoi(m_value[2].c_str())); + m_volume_facets.emplace_back(atoi(m_value[0].c_str()), atoi(m_value[1].c_str()), atoi(m_value[2].c_str())); m_value[0].clear(); m_value[1].clear(); m_value[2].clear(); @@ -621,44 +617,36 @@ void AMFParserContext::endElement(const char * /* name */) case NODE_TYPE_VOLUME: { assert(m_object && m_volume); - TriangleMesh mesh; - stl_file &stl = mesh.stl; - stl.stats.type = inmemory; - stl.stats.number_of_facets = int(m_volume_facets.size() / 3); - stl.stats.original_num_facets = stl.stats.number_of_facets; - stl_allocate(&stl); - - bool has_transform = ! m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); - for (size_t i = 0; i < m_volume_facets.size();) { - stl_facet &facet = stl.facet_start[i/3]; - for (unsigned int v = 0; v < 3; ++v) - { - unsigned int tri_id = m_volume_facets[i++] * 3; - if (tri_id < 0 || tri_id + 2 >= m_object_vertices.size()) { + // Verify validity of face indices. + for (Vec3i face : m_volume_facets) + for (unsigned int tri_id : face) + if (tri_id < 0 || tri_id >= m_object_vertices.size()) { this->stop("Malformed triangle mesh"); return; } - facet.vertex[v] = Vec3f(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]); - } - } - stl_get_size(&stl); - mesh.repair(); - m_volume->set_mesh(std::move(mesh)); - // stores the volume matrix taken from the metadata, if present - if (has_transform) - m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform); - if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) + { + TriangleMesh triangle_mesh { std::move(m_object_vertices), std::move(m_volume_facets) }; + if (triangle_mesh.volume() < 0) + triangle_mesh.flip_triangles(); + m_volume->set_mesh(std::move(triangle_mesh)); + } + + // stores the volume matrix taken from the metadata, if present + if (bool has_transform = !m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); has_transform) + m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform); + + if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) { m_volume->source.object_idx = (int)m_model.objects.size() - 1; m_volume->source.volume_idx = (int)m_model.objects.back()->volumes.size() - 1; m_volume->center_geometry_after_creation(); - } - else + } else // pass false if the mesh offset has been already taken from the data m_volume->center_geometry_after_creation(m_volume->source.input_file.empty()); m_volume->calculate_convex_hull(); m_volume_facets.clear(); + m_object_vertices.clear(); m_volume = nullptr; break; } @@ -1187,10 +1175,6 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, int num_vertices = 0; for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); - if (! volume->mesh().repaired) - throw Slic3r::FileIOError("store_amf() requires repair()"); - if (! volume->mesh().has_shared_vertices()) - throw Slic3r::FileIOError("store_amf() requires shared vertices"); const indexed_triangle_set &its = volume->mesh().its; const Transform3d& matrix = volume->get_matrix(); for (size_t i = 0; i < its.vertices.size(); ++i) { diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index cb7eb45493..54c373ce32 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -19,7 +19,8 @@ namespace Slic3r { bool load_obj(const char *path, TriangleMesh *meshptr) { - if(meshptr == nullptr) return false; + if (meshptr == nullptr) + return false; // Parse the OBJ file. ObjParser::ObjData data; @@ -31,84 +32,69 @@ bool load_obj(const char *path, TriangleMesh *meshptr) // Count the faces and verify, that all faces are triangular. size_t num_faces = 0; size_t num_quads = 0; - for (size_t i = 0; i < data.vertices.size(); ) { + for (size_t i = 0; i < data.vertices.size(); ++ i) { + // Find the end of face. size_t j = i; for (; j < data.vertices.size() && data.vertices[j].coordIdx != -1; ++ j) ; - if (i == j) - continue; - size_t face_vertices = j - i; - if (face_vertices != 3 && face_vertices != 4) { - // Non-triangular and non-quad faces are not supported as of now. - return false; + if (size_t num_face_vertices = j - i; num_face_vertices > 0) { + if (num_face_vertices > 4) { + // Non-triangular and non-quad faces are not supported as of now. + BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with more than 4 vertices."; + return false; + } else if (num_face_vertices < 3) { + // Non-triangular and non-quad faces are not supported as of now. + BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains polygons with less than 2 vertices."; + return false; + } + if (num_face_vertices == 4) + ++ num_quads; + ++ num_faces; + i = j; } - if (face_vertices == 4) - ++ num_quads; - ++ num_faces; - i = j + 1; } - // Convert ObjData into STL. - TriangleMesh &mesh = *meshptr; - stl_file &stl = mesh.stl; - stl.stats.type = inmemory; - stl.stats.number_of_facets = uint32_t(num_faces + num_quads); - stl.stats.original_num_facets = int(num_faces + num_quads); - // stl_allocate clears all the allocated data to zero, all normals are set to zeros as well. - stl_allocate(&stl); - size_t i_face = 0; - for (size_t i = 0; i < data.vertices.size(); ++ i) { - if (data.vertices[i].coordIdx == -1) - continue; - stl_facet &facet = stl.facet_start[i_face ++]; - size_t num_normals = 0; - stl_normal normal(stl_normal::Zero()); - for (unsigned int v = 0; v < 3; ++ v) { - const ObjParser::ObjVertex &vertex = data.vertices[i++]; - memcpy(facet.vertex[v].data(), &data.coordinates[vertex.coordIdx*4], 3 * sizeof(float)); - if (vertex.normalIdx != -1) { - normal(0) += data.normals[vertex.normalIdx*3]; - normal(1) += data.normals[vertex.normalIdx*3+1]; - normal(2) += data.normals[vertex.normalIdx*3+2]; - ++ num_normals; - } - } - // Result of obj_parseline() call is not checked, thus not all vertices are necessarily finalized with coord_Idx == -1. - if (i < data.vertices.size() && data.vertices[i].coordIdx != -1) { - // This is a quad. Produce the other triangle. - stl_facet &facet2 = stl.facet_start[i_face++]; - facet2.vertex[0] = facet.vertex[0]; - facet2.vertex[1] = facet.vertex[2]; - const ObjParser::ObjVertex &vertex = data.vertices[i++]; - memcpy(facet2.vertex[2].data(), &data.coordinates[vertex.coordIdx * 4], 3 * sizeof(float)); - if (vertex.normalIdx != -1) { - normal(0) += data.normals[vertex.normalIdx*3]; - normal(1) += data.normals[vertex.normalIdx*3+1]; - normal(2) += data.normals[vertex.normalIdx*3+2]; - ++ num_normals; - } - if (num_normals == 4) { - // Normalize an average normal of a quad. - float len = facet.normal.norm(); - if (len > EPSILON) { - normal /= len; - facet.normal = normal; - facet2.normal = normal; - } - } - } else if (num_normals == 3) { - // Normalize an average normal of a triangle. - float len = facet.normal.norm(); - if (len > EPSILON) - facet.normal = normal / len; - } + // Convert ObjData into indexed triangle set. + indexed_triangle_set its; + size_t num_vertices = data.coordinates.size() / 4; + its.vertices.reserve(num_vertices); + its.indices.reserve(num_faces + num_quads); + for (size_t i = 0; i < num_vertices; ++ i) { + size_t j = i << 2; + its.vertices.emplace_back(data.coordinates[j], data.coordinates[j + 1], data.coordinates[j + 2]); } - stl_get_size(&stl); - mesh.repair(); - if (mesh.facets_count() == 0) { + int indices[4]; + for (size_t i = 0; i < data.vertices.size();) + if (data.vertices[i].coordIdx == -1) + ++ i; + else { + int cnt = 0; + while (i < data.vertices.size()) + if (const ObjParser::ObjVertex &vertex = data.vertices[i ++]; vertex.coordIdx == -1) { + break; + } else { + assert(cnt < 4); + if (vertex.coordIdx < 0 || vertex.coordIdx >= its.vertices.size()) { + BOOST_LOG_TRIVIAL(error) << "load_obj: failed to parse " << path << ". The file contains invalid vertex index."; + return false; + } + indices[cnt ++] = vertex.coordIdx; + } + if (cnt) { + assert(cnt == 3 || cnt == 4); + // Insert one or two faces (triangulate a quad). + its.indices.emplace_back(indices[0], indices[1], indices[2]); + if (cnt == 4) + its.indices.emplace_back(indices[0], indices[2], indices[3]); + } + } + + *meshptr = TriangleMesh(std::move(its)); + if (meshptr->empty()) { BOOST_LOG_TRIVIAL(error) << "load_obj: This OBJ file couldn't be read because it's empty. " << path; return false; } - + if (meshptr->volume() < 0) + meshptr->flip_triangles(); return true; } diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp deleted file mode 100644 index 586fbafb52..0000000000 --- a/src/libslic3r/Format/PRUS.cpp +++ /dev/null @@ -1,333 +0,0 @@ -#include -#include - -#include - -#include "miniz_extension.hpp" - -#include - -#include "../libslic3r.h" -#include "../Model.hpp" - -#include "PRUS.hpp" - -#if 0 -// Enable debugging and assert in this file. -#define DEBUG -#define _DEBUG -#undef NDEBUG -#endif - -#include - -namespace Slic3r -{ - -struct StlHeader -{ - char comment[80]; - uint32_t nTriangles; -}; - -static_assert(sizeof(StlHeader) == 84, "StlHeader size not correct"); - -// Buffered line reader to a string buffer. -class LineReader -{ -public: - LineReader(std::vector &data) : m_buffer(data), m_pos(0), m_len((int)data.size()) {} - - const char* next_line() { - // Skip empty lines. - while (m_pos < m_len && (m_buffer[m_pos] == '\r' || m_buffer[m_pos] == '\n')) - ++ m_pos; - if (m_pos == m_len) { - // End of file. - return nullptr; - } - // The buffer is nonempty and it does not start with end of lines. Find the first end of line. - int end = m_pos + 1; - while (end < m_len && m_buffer[end] != '\r' && m_buffer[end] != '\n') - ++ end; - char *ptr_out = m_buffer.data() + m_pos; - m_pos = end + 1; - m_buffer[end] = 0; - return ptr_out; - } - - int next_line_scanf(const char *format, ...) - { - const char *line = next_line(); - if (line == nullptr) - return -1; - int result; - va_list arglist; - va_start(arglist, format); - result = vsscanf(line, format, arglist); - va_end(arglist); - return result; - } - -private: - std::vector &m_buffer; - int m_pos; - int m_len; -}; - -static void extract_model_from_archive( - // name of the model file - const char *name, - // path to the archive - const char *path, - // content of scene.xml - const std::vector &scene_xml_data, - // loaded data of this STL - std::vector &data, - // Model, to which the newly loaded objects will be added - Model *model, - // To map multiple STLs into a single model object for multi-material prints. - std::map &group_to_model_object) -{ - // Find the model entry in the XML data. - char model_name_tag[1024]; - sprintf(model_name_tag, "", name); - const char *model_xml = strstr(scene_xml_data.data(), model_name_tag); - const char *zero_tag = ""; - const char *zero_xml = strstr(scene_xml_data.data(), zero_tag); - Vec3d instance_rotation = Vec3d::Zero(); - Vec3d instance_scaling_factor = Vec3d::Ones(); - Vec3d instance_offset = Vec3d::Zero(); - bool trafo_set = false; - unsigned int group_id = (unsigned int)-1; - unsigned int extruder_id = (unsigned int)-1; - ModelObject *model_object = nullptr; - if (model_xml != nullptr) { - model_xml += strlen(model_name_tag); - const char *position_tag = ""; - const char *position_xml = strstr(model_xml, position_tag); - const char *rotation_tag = ""; - const char *rotation_xml = strstr(model_xml, rotation_tag); - const char *scale_tag = ""; - const char *scale_xml = strstr(model_xml, scale_tag); - float position[3], rotation[3], scale[3], zero[3]; - if (position_xml != nullptr && rotation_xml != nullptr && scale_xml != nullptr && zero_xml != nullptr && - sscanf(position_xml+strlen(position_tag), - "[%f, %f, %f]", position, position+1, position+2) == 3 && - sscanf(rotation_xml+strlen(rotation_tag), - "[%f, %f, %f]", rotation, rotation+1, rotation+2) == 3 && - sscanf(scale_xml+strlen(scale_tag), - "[%f, %f, %f]", scale, scale+1, scale+2) == 3 && - sscanf(zero_xml+strlen(zero_tag), - "[%f, %f, %f]", zero, zero+1, zero+2) == 3) { - instance_scaling_factor = Vec3d((double)scale[0], (double)scale[1], (double)scale[2]); - instance_rotation = Vec3d(-(double)rotation[0], -(double)rotation[1], -(double)rotation[2]); - instance_offset = Vec3d((double)(position[0] - zero[0]), (double)(position[1] - zero[1]), (double)(position[2] - zero[2])); - trafo_set = true; - } - const char *group_tag = ""; - const char *group_xml = strstr(model_xml, group_tag); - const char *extruder_tag = ""; - const char *extruder_xml = strstr(model_xml, extruder_tag); - if (group_xml != nullptr) { - int group = atoi(group_xml + strlen(group_tag)); - if (group > 0) { - group_id = group; - auto it = group_to_model_object.find(group_id); - if (it != group_to_model_object.end()) - model_object = it->second; - } - } - if (extruder_xml != nullptr) { - int e = atoi(extruder_xml + strlen(extruder_tag)); - if (e > 0) - extruder_id = e; - } - } - if (! trafo_set) - throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); - - // Extract the STL. - StlHeader header; - TriangleMesh mesh; - bool mesh_valid = false; - bool stl_ascii = false; - if (data.size() > sizeof(StlHeader)) { - memcpy((char*)&header, data.data(), sizeof(StlHeader)); - if (strncmp(header.comment, "solid ", 6) == 0) - stl_ascii = true; - else { - // Header has been extracted. Now read the faces. - stl_file &stl = mesh.stl; - stl.stats.type = inmemory; - stl.stats.number_of_facets = header.nTriangles; - stl.stats.original_num_facets = header.nTriangles; - stl_allocate(&stl); - if (header.nTriangles > 0 && data.size() == 50 * header.nTriangles + sizeof(StlHeader)) { - memcpy((char*)stl.facet_start.data(), data.data() + sizeof(StlHeader), 50 * header.nTriangles); - if (sizeof(stl_facet) > SIZEOF_STL_FACET) { - // The stl.facet_start is not packed tightly. Unpack the array of stl_facets. - unsigned char *data = (unsigned char*)stl.facet_start.data(); - for (size_t i = header.nTriangles - 1; i > 0; -- i) - memmove(data + i * sizeof(stl_facet), data + i * SIZEOF_STL_FACET, SIZEOF_STL_FACET); - } - // All the faces have been read. - stl_get_size(&stl); - mesh.repair(); - if (std::abs(stl.stats.min(2)) < EPSILON) - stl.stats.min(2) = 0.; - // Add a mesh to a model. - if (mesh.facets_count() > 0) - mesh_valid = true; - } - } - } else - stl_ascii = true; - - if (stl_ascii) { - // Try to parse ASCII STL. - char normal_buf[3][32]; - stl_facet facet; - std::vector facets; - LineReader line_reader(data); - std::string solid_name; - facet.extra[0] = facet.extra[1] = 0; - for (;;) { - const char *line = line_reader.next_line(); - if (line == nullptr) - // End of file. - break; - if (strncmp(line, "solid", 5) == 0) { - // Opening the "solid" block. - if (! solid_name.empty()) { - // Error, solid block is already open. - facets.clear(); - break; - } - solid_name = line + 5; - if (solid_name.empty()) - solid_name = "unknown"; - continue; - } - if (strncmp(line, "endsolid", 8) == 0) { - // Closing the "solid" block. - if (solid_name.empty()) { - // Error, no solid block is open. - facets.clear(); - break; - } - solid_name.clear(); - continue; - } - // Line has to start with the word solid. - int res_normal = sscanf(line, " facet normal %31s %31s %31s", normal_buf[0], normal_buf[1], normal_buf[2]); - assert(res_normal == 3); - int res_outer_loop = line_reader.next_line_scanf(" outer loop"); - assert(res_outer_loop == 0); - int res_vertex1 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[0](0), &facet.vertex[0](1), &facet.vertex[0](2)); - assert(res_vertex1 == 3); - int res_vertex2 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[1](0), &facet.vertex[1](1), &facet.vertex[1](2)); - assert(res_vertex2 == 3); - int res_vertex3 = line_reader.next_line_scanf(" vertex %f %f %f", &facet.vertex[2](0), &facet.vertex[2](1), &facet.vertex[2](2)); - assert(res_vertex3 == 3); - int res_endloop = line_reader.next_line_scanf(" endloop"); - assert(res_endloop == 0); - int res_endfacet = line_reader.next_line_scanf(" endfacet"); - if (res_normal != 3 || res_outer_loop != 0 || res_vertex1 != 3 || res_vertex2 != 3 || res_vertex3 != 3 || res_endloop != 0 || res_endfacet != 0) { - // perror("Something is syntactically very wrong with this ASCII STL!"); - facets.clear(); - break; - } - // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. - if (sscanf(normal_buf[0], "%f", &facet.normal(0)) != 1 || - sscanf(normal_buf[1], "%f", &facet.normal(1)) != 1 || - sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) { - // Normal was mangled. Maybe denormals or "not a number" were stored? - // Just reset the normal and silently ignore it. - facet.normal = stl_normal::Zero(); - } - facets.emplace_back(facet); - } - if (! facets.empty() && solid_name.empty()) { - stl_file &stl = mesh.stl; - stl.stats.type = inmemory; - stl.stats.number_of_facets = (uint32_t)facets.size(); - stl.stats.original_num_facets = (int)facets.size(); - stl_allocate(&stl); - memcpy((void*)stl.facet_start.data(), facets.data(), facets.size() * 50); - stl_get_size(&stl); - mesh.repair(); - // Add a mesh to a model. - if (mesh.facets_count() > 0) - mesh_valid = true; - } - } - - if (! mesh_valid) - throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name); - - // Add this mesh to the model. - ModelVolume *volume = nullptr; - if (model_object == nullptr) { - // This is a first mesh of a group. Create a new object & volume. - model_object = model->add_object(name, path, std::move(mesh)); - volume = model_object->volumes.front(); - ModelInstance *instance = model_object->add_instance(); - instance->set_rotation(instance_rotation); - instance->set_scaling_factor(instance_scaling_factor); - instance->set_offset(instance_offset); - if (group_id != (unsigned int)(-1)) - group_to_model_object[group_id] = model_object; - } else { - // This is not the 1st mesh of a group. Add it to the ModelObject. - volume = model_object->add_volume(std::move(mesh)); - volume->name = name; - } - // Set the extruder to the volume. - if (extruder_id != (unsigned int)-1) - volume->config.set("extruder", int(extruder_id)); -} - -// Load a PrusaControl project file into a provided model. -bool load_prus(const char *path, Model *model) -{ - mz_zip_archive archive; - mz_zip_zero_struct(&archive); - - size_t n_models_initial = model->objects.size(); - mz_bool res = MZ_FALSE; - try { - if (!open_zip_reader(&archive, path)) - throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path); - std::vector scene_xml_data; - // For grouping multiple STLs into a single ModelObject for multi-material prints. - std::map group_to_model_object; - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - for (mz_uint i = 0; i < num_entries; ++ i) { - mz_zip_archive_file_stat stat; - if (! mz_zip_reader_file_stat(&archive, i, &stat)) - continue; - std::vector buffer; - buffer.assign((size_t)stat.m_uncomp_size, 0); - res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0); - if (res == MZ_FALSE) - throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path); - if (strcmp(stat.m_filename, "scene.xml") == 0) { - if (! scene_xml_data.empty()) - throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path); - scene_xml_data = std::move(buffer); - } else if (boost::iends_with(stat.m_filename, ".stl")) { - // May throw std::exception - extract_model_from_archive(stat.m_filename, path, scene_xml_data, buffer, model, group_to_model_object); - } - } - } catch (std::exception &ex) { - close_zip_reader(&archive); - throw ex; - } - - close_zip_reader(&archive); - return model->objects.size() > n_models_initial; -} - -}; // namespace Slic3r diff --git a/src/libslic3r/Format/PRUS.hpp b/src/libslic3r/Format/PRUS.hpp deleted file mode 100644 index be5c5c61ac..0000000000 --- a/src/libslic3r/Format/PRUS.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#define slic3r_Format_PRUS_hpp_ - -namespace Slic3r { - -class TriangleMesh; -class Model; - -// Load a PrusaControl project file into a provided model. -extern bool load_prus(const char *path, Model *model); - -}; // namespace Slic3r diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp index 932906fe0e..2f2c9ec7fb 100644 --- a/src/libslic3r/Format/STL.cpp +++ b/src/libslic3r/Format/STL.cpp @@ -21,8 +21,7 @@ bool load_stl(const char *path, Model *model, const char *object_name_in) // die "Failed to open $file\n" if !-e $path; return false; } - mesh.repair(); - if (mesh.facets_count() == 0) { + if (mesh.empty()) { // die "This STL file couldn't be read because it's empty.\n" return false; } diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index d26e085e49..64c9ba4280 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -841,26 +841,16 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_result.extruders_count = extruders_count; m_extruder_offsets.resize(extruders_count); - for (size_t i = 0; i < extruders_count; ++i) { - Vec2f offset = config.extruder_offset.get_at(i).cast(); - m_extruder_offsets[i] = { offset(0), offset(1), 0.0f }; - } - m_extruder_colors.resize(extruders_count); - for (size_t i = 0; i < extruders_count; ++i) { - m_extruder_colors[i] = static_cast(i); - } - + m_result.filament_diameters.resize(extruders_count); + m_result.filament_densities.resize(extruders_count); m_extruder_temps.resize(extruders_count); - m_result.filament_diameters.resize(config.filament_diameter.values.size()); - for (size_t i = 0; i < config.filament_diameter.values.size(); ++i) { - m_result.filament_diameters[i] = static_cast(config.filament_diameter.values[i]); - } - - m_result.filament_densities.resize(config.filament_density.values.size()); - for (size_t i = 0; i < config.filament_density.values.size(); ++i) { - m_result.filament_densities[i] = static_cast(config.filament_density.values[i]); + for (size_t i = 0; i < extruders_count; ++ i) { + m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast().eval(), 0.f); + m_extruder_colors[i] = static_cast(i); + m_result.filament_diameters[i] = static_cast(config.filament_diameter.get_at(i)); + m_result.filament_densities[i] = static_cast(config.filament_density.get_at(i)); } if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index a59165946a..25250e2341 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -29,18 +29,17 @@ TriangleMesh eigen_to_triangle_mesh(const EigenMesh &emesh) { auto &VC = emesh.first; auto &FC = emesh.second; - Pointf3s points(size_t(VC.rows())); - std::vector facets(size_t(FC.rows())); + indexed_triangle_set its; + its.vertices.reserve(size_t(VC.rows())); + its.indices.reserve(size_t(FC.rows())); for (Eigen::Index i = 0; i < VC.rows(); ++i) - points[size_t(i)] = VC.row(i); + its.vertices.emplace_back(VC.row(i).cast()); for (Eigen::Index i = 0; i < FC.rows(); ++i) - facets[size_t(i)] = FC.row(i); + its.indices.emplace_back(FC.row(i)); - TriangleMesh out{points, facets}; - out.require_shared_vertices(); - return out; + return TriangleMesh { std::move(its) }; } EigenMesh triangle_mesh_to_eigen(const TriangleMesh &mesh) @@ -131,28 +130,27 @@ void triangle_mesh_to_cgal(const std::vector & V, out.add_face(VI(f(0)), VI(f(1)), VI(f(2))); } -inline Vec3d to_vec3d(const _EpicMesh::Point &v) +inline Vec3f to_vec3f(const _EpicMesh::Point& v) { - return {v.x(), v.y(), v.z()}; + return { float(v.x()), float(v.y()), float(v.z()) }; } -inline Vec3d to_vec3d(const _EpecMesh::Point &v) +inline Vec3f to_vec3f(const _EpecMesh::Point& v) { CGAL::Cartesian_converter cvt; auto iv = cvt(v); - return {iv.x(), iv.y(), iv.z()}; + return { float(iv.x()), float(iv.y()), float(iv.z()) }; } template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) { - Pointf3s points; - std::vector facets; - points.reserve(cgalmesh.num_vertices()); - facets.reserve(cgalmesh.num_faces()); + indexed_triangle_set its; + its.vertices.reserve(cgalmesh.num_vertices()); + its.indices.reserve(cgalmesh.num_faces()); for (auto &vi : cgalmesh.vertices()) { auto &v = cgalmesh.point(vi); // Don't ask... - points.emplace_back(to_vec3d(v)); + its.vertices.emplace_back(to_vec3f(v)); } for (auto &face : cgalmesh.faces()) { @@ -166,12 +164,10 @@ template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) } if (i == 3) - facets.emplace_back(facet); + its.indices.emplace_back(facet); } - TriangleMesh out{points, facets}; - out.repair(); - return out; + return TriangleMesh(std::move(its)); } std::unique_ptr diff --git a/src/libslic3r/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index 7233ac40d6..c9df648fb9 100644 --- a/src/libslic3r/MeshSplitImpl.hpp +++ b/src/libslic3r/MeshSplitImpl.hpp @@ -28,65 +28,84 @@ template<> struct ItsWithNeighborsIndex_ { } }; -// Visit all unvisited neighboring facets that are reachable from the first unvisited facet, -// and return them. +// Discover connected patches of facets one by one. template -std::vector its_find_unvisited_neighbors( - const indexed_triangle_set &its, - const NeighborIndex & neighbor_index, - std::vector & visited) -{ - using stack_el = size_t; - - auto facestack = reserve_vector(its.indices.size()); - auto push = [&facestack] (const stack_el &s) { facestack.emplace_back(s); }; - auto pop = [&facestack] () -> stack_el { - stack_el ret = facestack.back(); - facestack.pop_back(); - return ret; - }; - - // find the next unvisited facet and push the index - auto facet = std::find(visited.begin(), visited.end(), false); - std::vector ret; - - if (facet != visited.end()) { - ret.reserve(its.indices.size()); - auto idx = size_t(facet - visited.begin()); - push(idx); - ret.emplace_back(idx); - visited[idx] = true; +struct NeighborVisitor { + NeighborVisitor(const indexed_triangle_set &its, const NeighborIndex &neighbor_index) : + its(its), neighbor_index(neighbor_index) { + m_visited.assign(its.indices.size(), false); + m_facestack.reserve(its.indices.size()); + } + NeighborVisitor(const indexed_triangle_set &its, NeighborIndex &&aneighbor_index) : + its(its), neighbor_index(m_neighbor_index_data), m_neighbor_index_data(std::move(aneighbor_index)) { + m_visited.assign(its.indices.size(), false); + m_facestack.reserve(its.indices.size()); } - while (!facestack.empty()) { - size_t facet_idx = pop(); - const auto &neighbors = neighbor_index[facet_idx]; - for (auto neighbor_idx : neighbors) { - if (size_t(neighbor_idx) < visited.size() && !visited[size_t(neighbor_idx)]) { - visited[size_t(neighbor_idx)] = true; - push(stack_el(neighbor_idx)); - ret.emplace_back(size_t(neighbor_idx)); + template + void visit(Visitor visitor) + { + // find the next unvisited facet and push the index + auto facet = std::find(m_visited.begin() + m_seed, m_visited.end(), false); + m_seed = facet - m_visited.begin(); + + if (facet != m_visited.end()) { + // Skip this element in the next round. + auto idx = m_seed ++; + if (! visitor(idx)) + return; + this->push(idx); + m_visited[idx] = true; + while (! m_facestack.empty()) { + size_t facet_idx = this->pop(); + for (auto neighbor_idx : neighbor_index[facet_idx]) { + assert(neighbor_idx < int(m_visited.size())); + if (neighbor_idx >= 0 && !m_visited[neighbor_idx]) { + if (! visitor(size_t(neighbor_idx))) + return; + m_visited[neighbor_idx] = true; + this->push(stack_el(neighbor_idx)); + } + } } } } - return ret; -} + const indexed_triangle_set &its; + const NeighborIndex &neighbor_index; + +private: + // If initialized with &&neighbor_index, take the ownership of the data. + const NeighborIndex m_neighbor_index_data; + + std::vector m_visited; + + using stack_el = size_t; + std::vector m_facestack; + void push(const stack_el &s) { m_facestack.emplace_back(s); } + stack_el pop() { stack_el ret = m_facestack.back(); m_facestack.pop_back(); return ret; } + + // Last face visited. + size_t m_seed { 0 }; +}; } // namespace meshsplit_detail +// Funky wrapper for timinig of its_split() using various neighbor index creating methods, see sandboxes/its_neighbor_index/main.cpp template struct ItsNeighborsWrapper { using Index = IndexT; - const indexed_triangle_set *its; - IndexT index; + const indexed_triangle_set &its; + const IndexT &index_ref; + const IndexT index; - ItsNeighborsWrapper(const indexed_triangle_set &m, IndexT &&idx) - : its{&m}, index{std::move(idx)} - {} + // Keeping a reference to index, the caller is responsible for keeping the index alive. + ItsNeighborsWrapper(const indexed_triangle_set &its, const IndexT &index) : its{its}, index_ref{index} {} + // Taking ownership of the index. + ItsNeighborsWrapper(const indexed_triangle_set &its, IndexT &&aindex) : its{its}, index_ref{index}, index(std::move(aindex)) {} - const auto& get_its() const noexcept { return *its; } - const auto& get_index() const noexcept { return index; } + const auto& get_its() const noexcept { return its; } + const auto& get_index() const noexcept { return index_ref; } }; // Splits a mesh into multiple meshes when possible. @@ -97,20 +116,19 @@ void its_split(const Its &m, OutputIt out_it) const indexed_triangle_set &its = ItsWithNeighborsIndex_::get_its(m); - std::vector visited(its.indices.size(), false); - struct VertexConv { size_t part_id = std::numeric_limits::max(); size_t vertex_image; }; std::vector vidx_conv(its.vertices.size()); - const auto& neighbor_index = ItsWithNeighborsIndex_::get_index(m); - + meshsplit_detail::NeighborVisitor visitor(its, meshsplit_detail::ItsWithNeighborsIndex_::get_index(m)); + + std::vector facets; for (size_t part_id = 0;; ++part_id) { - std::vector facets = - its_find_unvisited_neighbors(its, neighbor_index, visited); - + // Collect all faces of the next patch. + facets.clear(); + visitor.visit([&facets](size_t idx) { facets.emplace_back(idx); return true; }); if (facets.empty()) break; @@ -150,17 +168,34 @@ std::vector its_split(const Its &its) return ret; } -template bool its_is_splittable(const Its &m) +template +bool its_is_splittable(const Its &m) { - using namespace meshsplit_detail; - const indexed_triangle_set &its = ItsWithNeighborsIndex_::get_its(m); - const auto& neighbor_index = ItsWithNeighborsIndex_::get_index(m); + meshsplit_detail::NeighborVisitor visitor(meshsplit_detail::ItsWithNeighborsIndex_::get_its(m), meshsplit_detail::ItsWithNeighborsIndex_::get_index(m)); + bool has_some = false; + bool has_some2 = false; + // Traverse the 1st patch fully. + visitor.visit([&has_some](size_t idx) { has_some = true; return true; }); + if (has_some) + // Just check whether there is any face of the 2nd patch. + visitor.visit([&has_some2](size_t idx) { has_some2 = true; return false; }); + return has_some && has_some2; +} - std::vector visited(its.indices.size(), false); - its_find_unvisited_neighbors(its, neighbor_index, visited); - auto faces = its_find_unvisited_neighbors(its, neighbor_index, visited); - - return !faces.empty(); +template +size_t its_number_of_patches(const Its &m) +{ + meshsplit_detail::NeighborVisitor visitor(meshsplit_detail::ItsWithNeighborsIndex_::get_its(m), meshsplit_detail::ItsWithNeighborsIndex_::get_index(m)); + size_t num_patches = 0; + for (;;) { + bool has_some = false; + // Traverse the 1st patch fully. + visitor.visit([&has_some](size_t idx) { has_some = true; return true; }); + if (! has_some) + break; + ++ num_patches; + } + return num_patches; } template diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2844f644c0..8337ab3846 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -475,10 +475,10 @@ bool Model::looks_like_imperial_units() const void Model::convert_from_imperial_units(bool only_small_volumes) { - static constexpr const double in_to_mm = 25.4; + static constexpr const float in_to_mm = 25.4f; for (ModelObject* obj : this->objects) if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_inches) { - obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); + obj->scale_mesh_after_creation(in_to_mm); for (ModelVolume* v : obj->volumes) { assert(! v->source.is_converted_from_meters); v->source.is_converted_from_inches = true; @@ -505,7 +505,7 @@ void Model::convert_from_meters(bool only_small_volumes) static constexpr const double m_to_mm = 1000; for (ModelObject* obj : this->objects) if (! only_small_volumes || obj->get_object_stl_stats().volume < volume_threshold_meters) { - obj->scale_mesh_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm)); + obj->scale_mesh_after_creation(m_to_mm); for (ModelVolume* v : obj->volumes) { assert(! v->source.is_converted_from_inches); v->source.is_converted_from_meters = true; @@ -1062,11 +1062,11 @@ void ModelObject::mirror(Axis axis) } // This method could only be called before the meshes of this ModelVolumes are not shared! -void ModelObject::scale_mesh_after_creation(const Vec3d &versor) +void ModelObject::scale_mesh_after_creation(const float scale) { for (ModelVolume *v : this->volumes) { - v->scale_geometry_after_creation(versor); - v->set_offset(versor.cwiseProduct(v->get_offset())); + v->scale_geometry_after_creation(scale); + v->set_offset(Vec3d(scale, scale, scale).cwiseProduct(v->get_offset())); } this->invalidate_bounding_box(); } @@ -1077,9 +1077,8 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con ModelObject* new_object = new_clone(*this); - double koef = conv_type == ConversionType::CONV_FROM_INCH ? 25.4 : conv_type == ConversionType::CONV_TO_INCH ? 0.0393700787 : - conv_type == ConversionType::CONV_FROM_METER ? 1000 : conv_type == ConversionType::CONV_TO_METER ? 0.001 : 1; - const Vec3d versor = Vec3d(koef, koef, koef); + float koef = conv_type == ConversionType::CONV_FROM_INCH ? 25.4f : conv_type == ConversionType::CONV_TO_INCH ? 0.0393700787f : + conv_type == ConversionType::CONV_FROM_METER ? 1000.f : conv_type == ConversionType::CONV_TO_METER ? 0.001f : 1.f; new_object->set_model(nullptr); new_object->sla_support_points.clear(); @@ -1092,7 +1091,6 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con for (ModelVolume* volume : volumes) { if (!volume->mesh().empty()) { TriangleMesh mesh(volume->mesh()); - mesh.require_shared_vertices(); ModelVolume* vol = new_object->add_volume(mesh); vol->name = volume->name; @@ -1118,8 +1116,8 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con if (//vol->source.is_converted_from_inches != from_imperial && (volume_idxs.empty() || std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end())) { - vol->scale_geometry_after_creation(versor); - vol->set_offset(versor.cwiseProduct(volume->get_offset())); + vol->scale_geometry_after_creation(koef); + vol->set_offset(Vec3d(koef, koef, koef).cwiseProduct(volume->get_offset())); if (conv_type == ConversionType::CONV_FROM_INCH || conv_type == ConversionType::CONV_TO_INCH) vol->source.is_converted_from_inches = conv_type == ConversionType::CONV_FROM_INCH; if (conv_type == ConversionType::CONV_FROM_METER || conv_type == ConversionType::CONV_TO_METER) @@ -1164,14 +1162,6 @@ size_t ModelObject::parts_count() const return num; } -bool ModelObject::needed_repair() const -{ - for (const ModelVolume *v : this->volumes) - if (v->is_model_part() && v->mesh().needed_repair()) - return true; - return false; -} - ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes) { if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) @@ -1253,21 +1243,14 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr TriangleMesh upper_mesh, lower_mesh; { indexed_triangle_set upper_its, lower_its; - mesh.require_shared_vertices(); cut_mesh(mesh.its, float(z), &upper_its, &lower_its); - if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) upper_mesh = TriangleMesh(upper_its); - upper_mesh.repair(); - upper_mesh.reset_repair_stats(); - } - if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + if (attributes.has(ModelObjectCutAttribute::KeepLower)) lower_mesh = TriangleMesh(lower_its); - lower_mesh.repair(); - lower_mesh.reset_repair_stats(); - } } - if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper_mesh.facets_count() > 0) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && ! upper_mesh.empty()) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; // Don't copy the config's ID. @@ -1276,7 +1259,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttr assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); } - if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower_mesh.facets_count() > 0) { + if (attributes.has(ModelObjectCutAttribute::KeepLower) && ! lower_mesh.empty()) { ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; // Don't copy the config's ID. @@ -1346,24 +1329,22 @@ void ModelObject::split(ModelObjectPtrs* new_objects) if (volume->type() != ModelVolumeType::MODEL_PART) continue; - TriangleMeshPtrs meshptrs = volume->mesh().split(); + std::vector meshes = volume->mesh().split(); size_t counter = 1; - for (TriangleMesh* mesh : meshptrs) { - + for (TriangleMesh &mesh : meshes) { // FIXME: crashes if not satisfied - if (mesh->facets_count() < 3) continue; - - mesh->repair(); + if (mesh.facets_count() < 3) + continue; // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? ModelObject* new_object = m_model->add_object(); - if (meshptrs.size() == 1) { + if (meshes.size() == 1) { new_object->name = volume->name; // Don't copy the config's ID. new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config); } else { - new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : ""); + new_object->name = this->name + (meshes.size() > 1 ? "_" + std::to_string(counter++) : ""); // Don't copy the config's ID. new_object->config.assign_config(this->config); } @@ -1372,7 +1353,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) new_object->instances.reserve(this->instances.size()); for (const ModelInstance* model_instance : this->instances) new_object->add_instance(*model_instance); - ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); + ModelVolume* new_vol = new_object->add_volume(*volume, std::move(mesh)); for (ModelInstance* model_instance : new_object->instances) { @@ -1384,7 +1365,6 @@ void ModelObject::split(ModelObjectPtrs* new_objects) // reset the source to disable reload from disk new_vol->source = ModelVolume::Source(); new_objects->emplace_back(new_object); - delete mesh; } } } @@ -1402,7 +1382,6 @@ void ModelObject::merge() for (ModelVolume* volume : volumes) if (!volume->mesh().empty()) mesh.merge(volume->mesh()); - mesh.repair(); this->clear_volumes(); ModelVolume* vol = this->add_volume(mesh); @@ -1569,7 +1548,6 @@ void ModelObject::print_info() const boost::nowide::cout << "[" << boost::filesystem::path(this->input_file).filename().string() << "]" << endl; TriangleMesh mesh = this->raw_mesh(); - mesh.check_topology(); BoundingBoxf3 bb = mesh.bounding_box(); Vec3d size = bb.size(); cout << "size_x = " << size(0) << endl; @@ -1582,19 +1560,18 @@ void ModelObject::print_info() const cout << "max_y = " << bb.max(1) << endl; cout << "max_z = " << bb.max(2) << endl; cout << "number_of_facets = " << mesh.facets_count() << endl; - cout << "manifold = " << (mesh.is_manifold() ? "yes" : "no") << endl; + + cout << "manifold = " << (mesh.stats().manifold() ? "yes" : "no") << endl; + if (! mesh.stats().manifold()) + cout << "open_edges = " << mesh.stats().open_edges << endl; - mesh.repair(); // this calculates number_of_parts - if (mesh.needed_repair()) { - mesh.repair(); + if (mesh.stats().repaired()) { if (mesh.stats().degenerate_facets > 0) cout << "degenerate_facets = " << mesh.stats().degenerate_facets << endl; if (mesh.stats().edges_fixed > 0) cout << "edges_fixed = " << mesh.stats().edges_fixed << endl; if (mesh.stats().facets_removed > 0) cout << "facets_removed = " << mesh.stats().facets_removed << endl; - if (mesh.stats().facets_added > 0) - cout << "facets_added = " << mesh.stats().facets_added << endl; if (mesh.stats().facets_reversed > 0) cout << "facets_reversed = " << mesh.stats().facets_reversed << endl; if (mesh.stats().backwards_edges > 0) @@ -1624,24 +1601,23 @@ std::string ModelObject::get_export_filename() const return ret; } -stl_stats ModelObject::get_object_stl_stats() const +TriangleMeshStats ModelObject::get_object_stl_stats() const { if (this->volumes.size() == 1) return this->volumes[0]->mesh().stats(); - stl_stats full_stats; + TriangleMeshStats full_stats; full_stats.volume = 0.f; // fill full_stats from all objet's meshes for (ModelVolume* volume : this->volumes) { - const stl_stats& stats = volume->mesh().stats(); + const TriangleMeshStats& stats = volume->mesh().stats(); // initialize full_stats (for repaired errors) full_stats.degenerate_facets += stats.degenerate_facets; full_stats.edges_fixed += stats.edges_fixed; full_stats.facets_removed += stats.facets_removed; - full_stats.facets_added += stats.facets_added; full_stats.facets_reversed += stats.facets_reversed; full_stats.backwards_edges += stats.backwards_edges; @@ -1660,10 +1636,10 @@ int ModelObject::get_mesh_errors_count(const int vol_idx /*= -1*/) const if (vol_idx >= 0) return this->volumes[vol_idx]->get_mesh_errors_count(); - const stl_stats& stats = get_object_stl_stats(); + const TriangleMeshStats& stats = get_object_stl_stats(); return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; + stats.facets_reversed + stats.backwards_edges; } void ModelVolume::set_material_id(t_model_material_id material_id) @@ -1727,14 +1703,15 @@ void ModelVolume::center_geometry_after_creation(bool update_source_offset) void ModelVolume::calculate_convex_hull() { m_convex_hull = std::make_shared(this->mesh().convex_hull_3d()); + assert(m_convex_hull.get()); } int ModelVolume::get_mesh_errors_count() const { - const stl_stats &stats = this->mesh().stats(); + const TriangleMeshStats &stats = this->mesh().stats(); return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; + stats.facets_reversed + stats.backwards_edges; } const TriangleMesh& ModelVolume::get_convex_hull() const @@ -1782,11 +1759,9 @@ std::string ModelVolume::type_to_string(const ModelVolumeType t) // This is useful to assign different materials to different volumes of an object. size_t ModelVolume::split(unsigned int max_extruders) { - TriangleMeshPtrs meshptrs = this->mesh().split(); - if (meshptrs.size() <= 1) { - delete meshptrs.front(); + std::vector meshes = this->mesh().split(); + if (meshes.size() <= 1) return 1; - } size_t idx = 0; size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin(); @@ -1795,15 +1770,14 @@ size_t ModelVolume::split(unsigned int max_extruders) unsigned int extruder_counter = 0; Vec3d offset = this->get_offset(); - for (TriangleMesh *mesh : meshptrs) { - mesh->repair(); - if (mesh->empty()) + for (TriangleMesh &mesh : meshes) { + if (mesh.empty()) // Repair may have removed unconnected triangles, thus emptying the mesh. continue; if (idx == 0) { - this->set_mesh(std::move(*mesh)); + this->set_mesh(std::move(mesh)); this->calculate_convex_hull(); // Assign a new unique ID, so that a new GLVolume will be generated. this->set_new_unique_id(); @@ -1811,7 +1785,7 @@ size_t ModelVolume::split(unsigned int max_extruders) this->source = ModelVolume::Source(); } else - this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh))); + this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(mesh))); this->object->volumes[ivolume]->set_offset(Vec3d::Zero()); this->object->volumes[ivolume]->center_geometry_after_creation(); @@ -1819,7 +1793,6 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); this->object->volumes[ivolume]->m_is_splittable = 0; - delete mesh; ++ idx; } @@ -1888,7 +1861,7 @@ void ModelVolume::mirror(Axis axis) } // This method could only be called before the meshes of this ModelVolumes are not shared! -void ModelVolume::scale_geometry_after_creation(const Vec3d& versor) +void ModelVolume::scale_geometry_after_creation(const Vec3f& versor) { const_cast(m_mesh.get())->scale(versor); const_cast(m_convex_hull.get())->scale(versor); @@ -1921,8 +1894,7 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand void ModelVolume::convert_from_imperial_units() { assert(! this->source.is_converted_from_meters); - double in_to_mm = 25.4; - this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); + this->scale_geometry_after_creation(25.4f); this->set_offset(Vec3d(0, 0, 0)); this->source.is_converted_from_inches = true; } @@ -1930,8 +1902,7 @@ void ModelVolume::convert_from_imperial_units() void ModelVolume::convert_from_meters() { assert(! this->source.is_converted_from_inches); - double m_to_mm = 1000; - this->scale_geometry_after_creation(Vec3d(m_to_mm, m_to_mm, m_to_mm)); + this->scale_geometry_after_creation(1000.f); this->set_offset(Vec3d(0, 0, 0)); this->source.is_converted_from_meters = true; } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 07274d3521..ea1d0ed175 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -346,13 +346,12 @@ public: void mirror(Axis axis); // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_mesh_after_creation(const Vec3d& versor); + void scale_mesh_after_creation(const float scale); void convert_units(ModelObjectPtrs&new_objects, ConversionType conv_type, std::vector volume_idxs); size_t materials_count() const; size_t facets_count() const; size_t parts_count() const; - bool needed_repair() const; ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); void merge(); @@ -376,7 +375,7 @@ public: std::string get_export_filename() const; // Get full stl statistics for all object's meshes - stl_stats get_object_stl_stats() const; + TriangleMeshStats get_object_stl_stats() const; // Get count of errors in the mesh( or all object's meshes, if volume index isn't defined) int get_mesh_errors_count(const int vol_idx = -1) const; @@ -620,6 +619,8 @@ public: const TriangleMesh& mesh() const { return *m_mesh.get(); } void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared(mesh); } void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } + void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared(mesh); } + void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared(std::move(mesh)); } void set_mesh(std::shared_ptr &mesh) { m_mesh = mesh; } void set_mesh(std::unique_ptr &&mesh) { m_mesh = std::move(mesh); } void reset_mesh() { m_mesh = std::make_shared(); } @@ -670,7 +671,8 @@ public: void mirror(Axis axis); // This method could only be called before the meshes of this ModelVolumes are not shared! - void scale_geometry_after_creation(const Vec3d& versor); + void scale_geometry_after_creation(const Vec3f &versor); + void scale_geometry_after_creation(const float scale) { this->scale_geometry_after_creation(Vec3f(scale, scale, scale)); } // Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box. // Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared! diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 55dae94309..9cac7f63bc 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -286,8 +286,6 @@ void cut_drainholes(std::vector & obj_slices, if (mesh.empty()) return; - mesh.require_shared_vertices(); - std::vector hole_slices = slice_mesh_ex(mesh.its, slicegrid, closing_radius, thr); if (obj_slices.size() != hole_slices.size()) @@ -316,7 +314,6 @@ void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags) remove_inside_triangles(mesh, interior); mesh.merge(TriangleMesh{interior.mesh}); - mesh.require_shared_vertices(); } // Get the distance of p to the interior's zero iso_surface. Interior should @@ -557,8 +554,7 @@ void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior, new_faces = {}; mesh = TriangleMesh{mesh.its}; - mesh.repaired = true; - mesh.require_shared_vertices(); + //FIXME do we want to repair the mesh? Are there duplicate vertices or flipped triangles? } }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp index 20804193e2..3ad7d62b1e 100644 --- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp +++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp @@ -33,7 +33,6 @@ inline void reproject_points_and_holes(ModelObject *object) if (!object || (!has_holes && !has_sppoints)) return; TriangleMesh rmsh = object->raw_mesh(); - rmsh.require_shared_vertices(); IndexedMesh emesh{rmsh}; if (has_sppoints) diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 5486741f26..196646dc9e 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -205,7 +205,6 @@ inline bool is_on_floor(const SLAPrintObjectConfig &cfg) std::vector get_chull_rotations(const TriangleMesh &mesh, size_t max_count) { TriangleMesh chull = mesh.convex_hull_3d(); - chull.require_shared_vertices(); double chull2d_area = chull.convex_hull().area(); double area_threshold = chull2d_area / (scaled(1e3) * scaled(1.)); @@ -299,7 +298,6 @@ struct RotfinderBoilerplate { static TriangleMesh get_mesh_to_rotate(const ModelObject &mo) { TriangleMesh mesh = mo.raw_mesh(); - mesh.require_shared_vertices(); ModelInstance *mi = mo.instances[0]; auto rotation = Vec3d::Zero(); @@ -437,7 +435,6 @@ Vec2d find_min_z_height_rotation(const ModelObject &mo, RotfinderBoilerplate<1000> bp{mo, params}; TriangleMesh chull = bp.mesh.convex_hull_3d(); - chull.require_shared_vertices(); auto inputs = reserve_vector(chull.its.indices.size()); auto rotcmp = [](const XYRotation &r1, const XYRotation &r2) { double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y]; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 61ff908d3b..a09f5ea98d 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -896,7 +896,6 @@ SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) obj = m_model_object->raw_mesh(); if (!obj.empty()) { obj.transform(m_trafo); - obj.require_shared_vertices(); } }) {} diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 0cd80f20b7..e11926c7ea 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -323,7 +323,6 @@ private: { support_tree_ptr = sla::SupportTree::create(*this, ctl); tree_mesh = TriangleMesh{support_tree_ptr->retrieve_mesh(sla::MeshType::Support)}; - tree_mesh.require_shared_vertices(); return support_tree_ptr; } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 4b377d9f11..adec5735a8 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -526,7 +526,6 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) } auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; - assert(mesh.has_shared_vertices()); po.m_model_slices = slice_mesh_ex(mesh.its, slice_grid, params, thr); sla::Interior *interior = po.m_hollowing_data ? diff --git a/src/libslic3r/SimplifyMesh.hpp b/src/libslic3r/SimplifyMesh.hpp index fb3e73d049..23eb343d1c 100644 --- a/src/libslic3r/SimplifyMesh.hpp +++ b/src/libslic3r/SimplifyMesh.hpp @@ -14,10 +14,8 @@ void simplify_mesh(indexed_triangle_set &); template void simplify_mesh(TriangleMesh &m, Args &&...a) { - m.require_shared_vertices(); simplify_mesh(m.its, std::forward(a)...); - m = TriangleMesh{m.its}; - m.require_shared_vertices(); + m = TriangleMesh{ std::move(m.its) }; } } // namespace Slic3r diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index dd1aaf46e1..94496bbf7b 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,8 +41,8 @@ //==================== #define ENABLE_2_4_0_ALPHA1 1 -// Enable the fix for exporting and importing to/from 3mf file of mirrored volumes -#define ENABLE_FIX_MIRRORED_VOLUMES_3MF_IMPORT_EXPORT (1 && ENABLE_2_4_0_ALPHA1) +// Enable implementation of retract acceleration in gcode processor +#define ENABLE_RETRACT_ACCELERATION (1 && ENABLE_2_4_0_ALPHA1) // Enable rendering seams (and other options) in preview using models #define ENABLE_SEAMS_USING_MODELS (1 && ENABLE_2_4_0_ALPHA1) // Enable save and save as commands to be enabled also when the plater is empty and allow to load empty projects diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index d289fca141..1e41c1be57 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -7,6 +7,7 @@ #include "Point.hpp" #include "Execution/ExecutionTBB.hpp" #include "Execution/ExecutionSeq.hpp" +#include "Utils.hpp" #include #include @@ -29,74 +30,51 @@ namespace Slic3r { -TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector &facets) : repaired(false) +static void update_bounding_box(const indexed_triangle_set &its, TriangleMeshStats &out) { - stl_file &stl = this->stl; - stl.stats.type = inmemory; - - // count facets and allocate memory - stl.stats.number_of_facets = (uint32_t)facets.size(); - stl.stats.original_num_facets = stl.stats.number_of_facets; - stl_allocate(&stl); - - for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { - stl_facet facet; - facet.vertex[0] = points[facets[i](0)].cast(); - facet.vertex[1] = points[facets[i](1)].cast(); - facet.vertex[2] = points[facets[i](2)].cast(); - facet.extra[0] = 0; - facet.extra[1] = 0; - - stl_normal normal; - stl_calculate_normal(normal, &facet); - stl_normalize_vector(normal); - facet.normal = normal; - - stl.facet_start[i] = facet; - } - stl_get_size(&stl); + BoundingBoxf3 bbox = Slic3r::bounding_box(its); + out.min = bbox.min.cast(); + out.max = bbox.max.cast(); + out.size = out.max - out.min; } -TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false) +static void fill_initial_stats(const indexed_triangle_set &its, TriangleMeshStats &out) { - stl.stats.type = inmemory; - - // count facets and allocate memory - stl.stats.number_of_facets = uint32_t(M.indices.size()); - stl.stats.original_num_facets = int(stl.stats.number_of_facets); - stl_allocate(&stl); - - for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { - stl_facet facet; - facet.vertex[0] = M.vertices[size_t(M.indices[i](0))]; - facet.vertex[1] = M.vertices[size_t(M.indices[i](1))]; - facet.vertex[2] = M.vertices[size_t(M.indices[i](2))]; - facet.extra[0] = 0; - facet.extra[1] = 0; - - stl_normal normal; - stl_calculate_normal(normal, &facet); - stl_normalize_vector(normal); - facet.normal = normal; - - stl.facet_start[i] = facet; - } - - stl_get_size(&stl); + out.number_of_facets = its.indices.size(); + out.volume = its_volume(its); + update_bounding_box(its, out); + + const std::vector face_neighbors = its_face_neighbors(its); + out.number_of_parts = its_number_of_patches(its, face_neighbors); + out.open_edges = its_num_open_edges(face_neighbors); +} + +TriangleMesh::TriangleMesh(const std::vector &vertices, const std::vector &faces) : its { faces, vertices } +{ + fill_initial_stats(this->its, m_stats); +} + +TriangleMesh::TriangleMesh(std::vector &&vertices, const std::vector &&faces) : its { std::move(faces), std::move(vertices) } +{ + fill_initial_stats(this->its, m_stats); +} + +TriangleMesh::TriangleMesh(const indexed_triangle_set &its) : its(its) +{ + fill_initial_stats(this->its, m_stats); +} + +TriangleMesh::TriangleMesh(indexed_triangle_set &&its) : its(std::move(its)) +{ + fill_initial_stats(this->its, m_stats); } // #define SLIC3R_TRACE_REPAIR -void TriangleMesh::repair(bool update_shared_vertices) +static void trianglemesh_repair_on_import(stl_file &stl) { - if (this->repaired) { - if (update_shared_vertices) - this->require_shared_vertices(); - return; - } - // admesh fails when repairing empty meshes - if (this->stl.stats.number_of_facets == 0) + if (stl.stats.number_of_facets == 0) return; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; @@ -105,9 +83,9 @@ void TriangleMesh::repair(bool update_shared_vertices) #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; #endif /* SLIC3R_TRACE_REPAIR */ - assert(stl_validate(&this->stl)); + assert(stl_validate(&stl)); stl_check_facets_exact(&stl); - assert(stl_validate(&this->stl)); + assert(stl_validate(&stl)); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); @@ -117,9 +95,11 @@ void TriangleMesh::repair(bool update_shared_vertices) float tolerance = (float)stl.stats.shortest_edge; float increment = (float)stl.stats.bounding_diameter / 10000.0f; int iterations = 2; - if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { - for (int i = 0; i < iterations; i++) { - if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { + if (stl.stats.connected_facets_3_edge < int(stl.stats.number_of_facets)) { + // Not a manifold, some triangles have unconnected edges. + for (int i = 0; i < iterations; ++ i) { + if (stl.stats.connected_facets_3_edge < int(stl.stats.number_of_facets)) { + // Still not a manifold, some triangles have unconnected edges. //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; @@ -133,7 +113,7 @@ void TriangleMesh::repair(bool update_shared_vertices) } } } - assert(stl_validate(&this->stl)); + assert(stl_validate(&stl)); // remove_unconnected if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { @@ -141,7 +121,7 @@ void TriangleMesh::repair(bool update_shared_vertices) BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; #endif /* SLIC3R_TRACE_REPAIR */ stl_remove_unconnected_facets(&stl); - assert(stl_validate(&this->stl)); + assert(stl_validate(&stl)); } // fill_holes @@ -162,97 +142,82 @@ void TriangleMesh::repair(bool update_shared_vertices) BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_directions(&stl); - assert(stl_validate(&this->stl)); + assert(stl_validate(&stl)); // normal_values #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; #endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_values(&stl); - assert(stl_validate(&this->stl)); + assert(stl_validate(&stl)); // always calculate the volume and reverse all normals if volume is negative #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; #endif /* SLIC3R_TRACE_REPAIR */ + // If the volume is negative, all the facets are flipped and added to stats.facets_reversed. stl_calculate_volume(&stl); - assert(stl_validate(&this->stl)); + assert(stl_validate(&stl)); // neighbors #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; #endif /* SLIC3R_TRACE_REPAIR */ stl_verify_neighbors(&stl); - assert(stl_validate(&this->stl)); - - this->repaired = true; + assert(stl_validate(&stl)); //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. - if (auto nr_degenerated = this->stl.stats.degenerate_facets; this->facets_count() > 0 && nr_degenerated > 0) - stl_check_facets_exact(&this->stl); + if (auto nr_degenerated = stl.stats.degenerate_facets; stl.stats.number_of_facets > 0 && nr_degenerated > 0) + stl_check_facets_exact(&stl); BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; +} - // This call should be quite cheap, a lot of code requires the indexed_triangle_set data structure, - // and it is risky to generate such a structure once the meshes are shared. Do it now. - this->its.clear(); - if (update_shared_vertices) - this->require_shared_vertices(); +bool TriangleMesh::ReadSTLFile(const char* input_file, bool repair) +{ + stl_file stl; + if (! stl_open(&stl, input_file)) + return false; + if (repair) + trianglemesh_repair_on_import(stl); + + m_stats.number_of_facets = stl.stats.number_of_facets; + m_stats.min = stl.stats.min; + m_stats.max = stl.stats.max; + m_stats.size = stl.stats.size; + m_stats.volume = stl.stats.volume; + + auto facets_w_1_bad_edge = stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge; + auto facets_w_2_bad_edge = stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge; + auto facets_w_3_bad_edge = stl.stats.number_of_facets - stl.stats.connected_facets_1_edge; + m_stats.open_edges = facets_w_1_bad_edge + facets_w_2_bad_edge * 2 + facets_w_3_bad_edge * 3; + + m_stats.edges_fixed = stl.stats.edges_fixed; + m_stats.degenerate_facets = stl.stats.degenerate_facets; + m_stats.facets_removed = stl.stats.facets_removed; + m_stats.facets_reversed = stl.stats.facets_reversed; + m_stats.backwards_edges = stl.stats.backwards_edges; + m_stats.number_of_parts = stl.stats.number_of_parts; + + stl_generate_shared_vertices(&stl, this->its); + return true; +} + +bool TriangleMesh::write_ascii(const char* output_file) +{ + return its_write_stl_ascii(output_file, "", this->its); +} + +bool TriangleMesh::write_binary(const char* output_file) +{ + return its_write_stl_binary(output_file, "", this->its); } float TriangleMesh::volume() { - if (this->stl.stats.volume == -1) - stl_calculate_volume(&this->stl); - return this->stl.stats.volume; -} - -void TriangleMesh::check_topology() -{ - // checking exact - stl_check_facets_exact(&stl); - stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); - stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); - stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); - - // checking nearby - //int last_edges_fixed = 0; - float tolerance = stl.stats.shortest_edge; - float increment = stl.stats.bounding_diameter / 10000.0; - int iterations = 2; - if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { - for (int i = 0; i < iterations; i++) { - if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { - //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); - stl_check_facets_nearby(&stl, tolerance); - //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); - //last_edges_fixed = stl.stats.edges_fixed; - tolerance += increment; - } else { - break; - } - } - } -} - -void TriangleMesh::reset_repair_stats() { - this->stl.stats.degenerate_facets = 0; - this->stl.stats.edges_fixed = 0; - this->stl.stats.facets_removed = 0; - this->stl.stats.facets_added = 0; - this->stl.stats.facets_reversed = 0; - this->stl.stats.backwards_edges = 0; - this->stl.stats.normals_fixed = 0; -} - -bool TriangleMesh::needed_repair() const -{ - return this->stl.stats.degenerate_facets > 0 - || this->stl.stats.edges_fixed > 0 - || this->stl.stats.facets_removed > 0 - || this->stl.stats.facets_added > 0 - || this->stl.stats.facets_reversed > 0 - || this->stl.stats.backwards_edges > 0; + if (m_stats.volume == -1) + m_stats.volume = its_volume(this->its); + return m_stats.volume; } void TriangleMesh::WriteOBJFile(const char* output_file) const @@ -262,133 +227,138 @@ void TriangleMesh::WriteOBJFile(const char* output_file) const void TriangleMesh::scale(float factor) { - stl_scale(&(this->stl), factor); - for (stl_vertex& v : this->its.vertices) - v *= factor; + this->scale(Vec3f(factor, factor, factor)); } -void TriangleMesh::scale(const Vec3d &versor) +void TriangleMesh::scale(const Vec3f &versor) { - stl_scale_versor(&this->stl, versor.cast()); - for (stl_vertex& v : this->its.vertices) { - v.x() *= versor.x(); - v.y() *= versor.y(); - v.z() *= versor.z(); + // Scale extents. + auto s = versor.array(); + m_stats.min.array() *= s; + m_stats.max.array() *= s; + // Scale size. + m_stats.size.array() *= s; + // Scale volume. + if (m_stats.volume > 0.0) + m_stats.volume *= s(0) * s(1) * s(2); + if (versor.x() == versor.y() && versor.x() == versor.z()) { + float s = versor.x(); + for (stl_vertex &v : this->its.vertices) + v *= s; + } else { + for (stl_vertex &v : this->its.vertices) { + v.x() *= versor.x(); + v.y() *= versor.y(); + v.z() *= versor.z(); + } + } +} + +void TriangleMesh::translate(const Vec3f &displacement) +{ + if (displacement.x() != 0.f || displacement.y() != 0.f || displacement.z() != 0.f) { + for (stl_vertex& v : this->its.vertices) + v += displacement; + m_stats.min += displacement; + m_stats.max += displacement; } } void TriangleMesh::translate(float x, float y, float z) { - if (x == 0.f && y == 0.f && z == 0.f) - return; - stl_translate_relative(&(this->stl), x, y, z); - stl_vertex shift(x, y, z); - for (stl_vertex& v : this->its.vertices) - v += shift; -} - -void TriangleMesh::translate(const Vec3f &displacement) -{ - translate(displacement(0), displacement(1), displacement(2)); + this->translate(Vec3f(x, y, z)); } void TriangleMesh::rotate(float angle, const Axis &axis) { - if (angle == 0.f) - return; - - // admesh uses degrees - angle = Slic3r::Geometry::rad2deg(angle); - - if (axis == X) { - stl_rotate_x(&this->stl, angle); - its_rotate_x(this->its, angle); - } else if (axis == Y) { - stl_rotate_y(&this->stl, angle); - its_rotate_y(this->its, angle); - } else if (axis == Z) { - stl_rotate_z(&this->stl, angle); - its_rotate_z(this->its, angle); + if (angle != 0.f) { + angle = Slic3r::Geometry::rad2deg(angle); + switch (axis) { + case X: its_rotate_x(this->its, angle); break; + case Y: its_rotate_y(this->its, angle); break; + case Z: its_rotate_z(this->its, angle); break; + default: assert(false); return; + } + update_bounding_box(this->its, this->m_stats); } } void TriangleMesh::rotate(float angle, const Vec3d& axis) { - if (angle == 0.f) - return; - - Vec3d axis_norm = axis.normalized(); - Transform3d m = Transform3d::Identity(); - m.rotate(Eigen::AngleAxisd(angle, axis_norm)); - stl_transform(&stl, m); - its_transform(its, m); + if (angle != 0.f) { + Vec3d axis_norm = axis.normalized(); + Transform3d m = Transform3d::Identity(); + m.rotate(Eigen::AngleAxisd(angle, axis_norm)); + its_transform(its, m); + update_bounding_box(this->its, this->m_stats); + } } -void TriangleMesh::mirror(const Axis &axis) +void TriangleMesh::mirror(const Axis axis) { - if (axis == X) { - stl_mirror_yz(&this->stl); + switch (axis) { + case X: + for (stl_vertex &v : its.vertices) + v.x() *= -1.f; + break; + case Y: + for (stl_vertex& v : this->its.vertices) + v.y() *= -1.0; + break; + case Z: for (stl_vertex &v : this->its.vertices) - v(0) *= -1.0; - } else if (axis == Y) { - stl_mirror_xz(&this->stl); - for (stl_vertex &v : this->its.vertices) - v(1) *= -1.0; - } else if (axis == Z) { - stl_mirror_xy(&this->stl); - for (stl_vertex &v : this->its.vertices) - v(2) *= -1.0; - } + v.z() *= -1.0; + break; + default: + assert(false); + return; + }; + its_flip_triangles(this->its); + int iaxis = int(axis); + std::swap(m_stats.min[iaxis], m_stats.max[iaxis]); + m_stats.min[iaxis] *= -1.0; + m_stats.max[iaxis] *= -1.0; } void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { - stl_transform(&stl, t); its_transform(its, t); - if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { - // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. - // As for the assert: the repair function would fix the normals, reversing would - // break them again. The caller should provide a mesh that does not need repair. - // The repair call is left here so things don't break more than they were. - assert(this->repaired); - this->repair(false); - stl_reverse_all_facets(&stl); - this->its.clear(); - this->require_shared_vertices(); - } + if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) + its_flip_triangles(its); + else + m_stats.volume = - m_stats.volume; + update_bounding_box(this->its, this->m_stats); } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) { - stl_transform(&stl, m); its_transform(its, m); - if (fix_left_handed && m.determinant() < 0.) { - // See comments in function above. - assert(this->repaired); - this->repair(false); - stl_reverse_all_facets(&stl); - this->its.clear(); - this->require_shared_vertices(); - } + if (fix_left_handed && m.determinant() < 0.) + its_flip_triangles(its); + else + m_stats.volume = - m_stats.volume; + update_bounding_box(this->its, this->m_stats); +} + +void TriangleMesh::flip_triangles() +{ + its_flip_triangles(its); + m_stats.volume = - m_stats.volume; } void TriangleMesh::align_to_origin() { - this->translate( - - this->stl.stats.min(0), - - this->stl.stats.min(1), - - this->stl.stats.min(2)); + this->translate(- m_stats.min(0), - m_stats.min(1), - m_stats.min(2)); } void TriangleMesh::rotate(double angle, Point* center) { - if (angle == 0.) - return; - Vec2f c = center->cast(); - this->translate(-c(0), -c(1), 0); - stl_rotate_z(&this->stl, (float)angle); - its_rotate_z(this->its, (float)angle); - this->translate(c(0), c(1), 0); + if (angle != 0.) { + Vec2f c = center->cast(); + this->translate(-c(0), -c(1), 0); + its_rotate_z(this->its, (float)angle); + this->translate(c(0), c(1), 0); + } } /** @@ -396,145 +366,36 @@ void TriangleMesh::rotate(double angle, Point* center) */ bool TriangleMesh::is_splittable() const { - std::vector visited; - find_unvisited_neighbors(visited); - - // Try finding an unvisited facet. If there are none, the mesh is not splittable. - auto it = std::find(visited.begin(), visited.end(), false); - return it != visited.end(); + return its_is_splittable(this->its); } -/** - * Visit all unvisited neighboring facets that are reachable from the first unvisited facet, - * and return them. - * - * @param facet_visited A reference to a vector of booleans. Contains whether or not a - * facet with the same index has been visited. - * @return A deque with all newly visited facets. - */ -std::deque TriangleMesh::find_unvisited_neighbors(std::vector &facet_visited) const +std::vector TriangleMesh::split() const { - // Make sure we're not operating on a broken mesh. - if (!this->repaired) - throw Slic3r::RuntimeError("find_unvisited_neighbors() requires repair()"); + std::vector itss = its_split(this->its); + std::vector out; + out.reserve(itss.size()); + for (indexed_triangle_set &m : itss) { + // The TriangleMesh constructor shall fill in the mesh statistics including volume. + out.emplace_back(std::move(m)); + if (TriangleMesh &triangle_mesh = out.back(); triangle_mesh.volume() < 0) + // Some source mesh parts may be incorrectly oriented. Correct them. + triangle_mesh.flip_triangles(); - // If the visited list is empty, populate it with false for every facet. - if (facet_visited.empty()) - facet_visited = std::vector(this->stl.stats.number_of_facets, false); - - // Find the first unvisited facet. - std::queue facet_queue; - std::deque facets; - auto facet = std::find(facet_visited.begin(), facet_visited.end(), false); - if (facet != facet_visited.end()) { - uint32_t idx = uint32_t(facet - facet_visited.begin()); - facet_queue.push(idx); - facet_visited[idx] = true; - facets.emplace_back(idx); } - - // Traverse all reachable neighbors and mark them as visited. - while (! facet_queue.empty()) { - uint32_t facet_idx = facet_queue.front(); - facet_queue.pop(); - for (int neighbor_idx : this->stl.neighbors_start[facet_idx].neighbor) - if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) { - facet_queue.push(uint32_t(neighbor_idx)); - facet_visited[neighbor_idx] = true; - facets.emplace_back(uint32_t(neighbor_idx)); - } - } - - return facets; -} - -/** - * Splits a mesh into multiple meshes when possible. - * - * @return A TriangleMeshPtrs with the newly created meshes. - */ -TriangleMeshPtrs TriangleMesh::split() const -{ - struct MeshAdder { - TriangleMeshPtrs &meshes; - MeshAdder(TriangleMeshPtrs &ptrs): meshes{ptrs} {} - void operator=(const indexed_triangle_set &its) - { - meshes.emplace_back(new TriangleMesh(its)); - } - }; - - TriangleMeshPtrs meshes; - if (has_shared_vertices()) { - its_split(its, MeshAdder{meshes}); - } else { - // Loop while we have remaining facets. - std::vector facet_visited; - for (;;) { - std::deque facets = find_unvisited_neighbors(facet_visited); - if (facets.empty()) - break; - - // Create a new mesh for the part that was just split off. - TriangleMesh* mesh = new TriangleMesh; - meshes.emplace_back(mesh); - mesh->stl.stats.type = inmemory; - mesh->stl.stats.number_of_facets = (uint32_t)facets.size(); - mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; - stl_allocate(&mesh->stl); - - // Assign the facets to the new mesh. - bool first = true; - for (auto facet = facets.begin(); facet != facets.end(); ++ facet) { - mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet]; - stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first); - } - } - } - - return meshes; + return out; } void TriangleMesh::merge(const TriangleMesh &mesh) { - // reset stats and metadata - int number_of_facets = this->stl.stats.number_of_facets; - this->its.clear(); - this->repaired = false; - - // update facet count and allocate more memory - this->stl.stats.number_of_facets = number_of_facets + mesh.stl.stats.number_of_facets; - this->stl.stats.original_num_facets = this->stl.stats.number_of_facets; - stl_reallocate(&this->stl); - - // copy facets - for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++ i) - this->stl.facet_start[number_of_facets + i] = mesh.stl.facet_start[i]; - - // update size - stl_get_size(&this->stl); + its_merge(this->its, mesh.its); + m_stats = m_stats.merge(mesh.m_stats); } // Calculate projection of the mesh into the XY plane, in scaled coordinates. //FIXME This could be extremely slow! Use it for tiny meshes only! ExPolygons TriangleMesh::horizontal_projection() const { - ClipperLib::Paths paths; - Polygon p; - p.points.assign(3, Point()); - auto delta = scaled(0.01); - std::vector deltas { delta, delta, delta }; - paths.reserve(this->stl.stats.number_of_facets); - for (const stl_facet &facet : this->stl.facet_start) { - p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1)); - p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1)); - p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1)); - p.make_counter_clockwise(); - paths.emplace_back(mittered_offset_path_scaled(p.points, deltas, 3.)); - } - - // the offset factor was tuned using groovemount.stl - return ClipperPaths_to_Slic3rExPolygons(paths); + return union_ex(project_mesh(this->its, Transform3d::Identity(), []() {})); } // 2D convex hull of a 3D mesh projected into the Z=0 plane. @@ -553,24 +414,16 @@ BoundingBoxf3 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.defined = true; - bb.min = this->stl.stats.min.cast(); - bb.max = this->stl.stats.max.cast(); + bb.min = m_stats.min.cast(); + bb.max = m_stats.max.cast(); return bb; } BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) const { BoundingBoxf3 bbox; - if (this->its.vertices.empty()) { - // Using the STL faces. - for (const stl_facet &facet : this->stl.facet_start) - for (size_t j = 0; j < 3; ++ j) - bbox.merge(trafo * facet.vertex[j].cast()); - } else { - // Using the shared vertices should be a bit quicker than using the STL faces. - for (const stl_vertex &v : this->its.vertices) - bbox.merge(trafo * v.cast()); - } + for (const stl_vertex &v : this->its.vertices) + bbox.merge(trafo * v.cast()); return bbox; } @@ -582,26 +435,16 @@ TriangleMesh TriangleMesh::convex_hull_3d() const std::vector src_vertices; try { - if (this->has_shared_vertices()) { #if REALfloat - qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); + qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); #else - src_vertices.reserve(this->its.vertices.size() * 3); - // We will now fill the vector with input points for computation: - for (const stl_vertex &v : this->its.vertices) - for (int i = 0; i < 3; ++ i) - src_vertices.emplace_back(v(i)); - qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); + src_vertices.reserve(this->its.vertices.size() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex &v : this->its.vertices) + for (int i = 0; i < 3; ++ i) + src_vertices.emplace_back(v(i)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); #endif - } else { - src_vertices.reserve(this->stl.facet_start.size() * 9); - // We will now fill the vector with input points for computation: - for (const stl_facet &f : this->stl.facet_start) - for (int i = 0; i < 3; ++ i) - for (int j = 0; j < 3; ++ j) - src_vertices.emplace_back(f.vertex[i](j)); - qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); - } } catch (...) { @@ -610,84 +453,75 @@ TriangleMesh TriangleMesh::convex_hull_3d() const } // Let's collect results: - Pointf3s dst_vertices; - std::vector facets; - auto facet_list = qhull.facetList().toStdVector(); - for (const orgQhull::QhullFacet& facet : facet_list) - { // iterate through facets - orgQhull::QhullVertexSet vertices = facet.vertices(); - for (int i = 0; i < 3; ++i) - { // iterate through facet's vertices - - orgQhull::QhullPoint p = vertices[i].point(); - const auto* coords = p.coordinates(); - dst_vertices.emplace_back(coords[0], coords[1], coords[2]); + std::vector dst_vertices; + std::vector dst_facets; + // Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices). + std::vector map_dst_vertices; +#ifndef NDEBUG + Vec3f centroid = Vec3f::Zero(); + for (auto pt : this->its.vertices) + centroid += pt; + centroid /= float(this->its.vertices.size()); +#endif // NDEBUG + for (const orgQhull::QhullFacet facet : qhull.facetList()) { + // Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID. + Vec3i indices; + int cnt = 0; + for (const orgQhull::QhullVertex vertex : facet.vertices()) { + int id = vertex.id(); + assert(id >= 0); + if (id >= int(map_dst_vertices.size())) + map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1); + if (int i = map_dst_vertices[id]; i == -1) { + // Allocate a new vertex. + i = int(dst_vertices.size()); + map_dst_vertices[id] = i; + orgQhull::QhullPoint pt(vertex.point()); + dst_vertices.emplace_back(pt[0], pt[1], pt[2]); + indices[cnt] = i; + } else { + // Reuse existing vertex. + indices[cnt] = i; + } + if (cnt ++ == 3) + break; + } + assert(cnt == 3); + if (cnt == 3) { + // QHull sorts vertices of a face lexicographically by their IDs, not by face normals. + // Calculate face normal based on the order of vertices. + Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]); + auto *n2 = facet.getBaseT()->normal; + auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2]; +#ifndef NDEBUG + Vec3f n3 = (dst_vertices[indices(0)] - centroid); + auto d3 = n.dot(n3); + assert((d < 0.f) == (d3 < 0.f)); +#endif // NDEBUG + // Get the face normal from QHull. + if (d < 0.f) + // Fix face orientation. + std::swap(indices[1], indices[2]); + dst_facets.emplace_back(indices); } - unsigned int size = (unsigned int)dst_vertices.size(); - facets.emplace_back(size - 3, size - 2, size - 1); } - TriangleMesh output_mesh(dst_vertices, facets); - output_mesh.repair(); - return output_mesh; + return TriangleMesh { std::move(dst_vertices), std::move(dst_facets) }; } std::vector TriangleMesh::slice(const std::vector &z) const { // convert doubles to floats std::vector z_f(z.begin(), z.end()); - assert(this->has_shared_vertices()); return slice_mesh_ex(this->its, z_f, 0.0004f); } -void TriangleMesh::require_shared_vertices() -{ - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; - assert(stl_validate(&this->stl)); - if (! this->repaired) - this->repair(); - if (this->its.vertices.empty()) { - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; - stl_generate_shared_vertices(&this->stl, this->its); - } - assert(stl_validate(&this->stl, this->its)); - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; -} - size_t TriangleMesh::memsize() const { - size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); + size_t memsize = 8 + this->its.memsize() + sizeof(this->m_stats); return memsize; } -// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. -size_t TriangleMesh::release_optional() -{ - size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); - // The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function. - this->its.clear(); - // The neighbors structure may be recalculated using the stl_check_facets_exact() function. - this->stl.neighbors_start.clear(); - return memsize_released; -} - -// Restore optional data possibly released by release_optional(). -void TriangleMesh::restore_optional() -{ - if (! this->stl.facet_start.empty()) { - // Save the old stats before calling stl_check_faces_exact, as it may modify the statistics. - stl_stats stats = this->stl.stats; - if (this->stl.neighbors_start.empty()) { - stl_reallocate(&this->stl); - stl_check_facets_exact(&this->stl); - } - if (this->its.vertices.empty()) - stl_generate_shared_vertices(&this->stl, this->its); - // Restore the old statistics. - this->stl.stats = stats; - } -} - // Create a mapping from triangle edge into face. struct EdgeToFace { // Index of the 1st vertex of the triangle edge. vertex_low <= vertex_high. @@ -1063,21 +897,32 @@ Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transfor indexed_triangle_set its_make_cube(double xd, double yd, double zd) { auto x = float(xd), y = float(yd), z = float(zd); - indexed_triangle_set mesh; - mesh.vertices = {{x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0}, - {x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z}}; - mesh.indices = {{0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7}, - {0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2}, - {2, 6, 5}, {2, 5, 3}, {4, 0, 3}, {4, 3, 5}}; - - return mesh; + return { + { {0, 1, 2}, {0, 2, 3}, {4, 5, 6}, {4, 6, 7}, + {0, 4, 7}, {0, 7, 1}, {1, 7, 6}, {1, 6, 2}, + {2, 6, 5}, {2, 5, 3}, {4, 0, 3}, {4, 3, 5} }, + { {x, y, 0}, {x, 0, 0}, {0, 0, 0}, {0, y, 0}, + {x, y, z}, {0, y, z}, {0, 0, z}, {x, 0, z} } + }; } -TriangleMesh make_cube(double x, double y, double z) +indexed_triangle_set its_make_prism(float width, float length, float height) { - TriangleMesh mesh(its_make_cube(x, y, z)); - mesh.repair(); - return mesh; + // We need two upward facing triangles + float x = width / 2.f, y = length / 2.f; + return { + { + {0, 1, 2}, // side 1 + {4, 3, 5}, // side 2 + {1, 4, 2}, {2, 4, 5}, // roof 1 + {0, 2, 5}, {0, 5, 3}, // roof 2 + {3, 4, 1}, {3, 1, 0} // bottom + }, + { + {-x, -y, 0.f}, {x, -y, 0.f}, {0.f, -y, height}, + {-x, y, 0.f}, {x, y, 0.f}, {0.f, y, height}, + } + }; } // Generate the mesh for a cylinder and return it, using @@ -1125,14 +970,6 @@ indexed_triangle_set its_make_cylinder(double r, double h, double fa) return mesh; } -TriangleMesh make_cylinder(double r, double h, double fa) -{ - TriangleMesh mesh{its_make_cylinder(r, h, fa)}; - mesh.repair(); - - return mesh; -} - indexed_triangle_set its_make_cone(double r, double h, double fa) { indexed_triangle_set mesh; @@ -1159,11 +996,23 @@ indexed_triangle_set its_make_cone(double r, double h, double fa) return mesh; } -TriangleMesh make_cone(double radius, double fa) +indexed_triangle_set its_make_pyramid(float base, float height) { - TriangleMesh mesh(its_make_cone(radius, fa)); - mesh.repair(); - return mesh; + float a = base / 2.f; + return { + { + {0, 1, 2}, + {0, 2, 3}, + {0, 1, 4}, + {1, 2, 4}, + {2, 3, 4}, + {3, 0, 4} + }, + { + {-a, -a, 0}, {a, -a, 0}, {a, a, 0}, + {-a, a, 0}, {0.f, 0.f, height} + } + }; } // Generates mesh for a sphere centered about the origin, using the generated angle @@ -1224,11 +1073,10 @@ indexed_triangle_set its_make_sphere(double radius, double fa) return mesh; } -TriangleMesh make_sphere(double radius, double fa) +void its_reverse_all_facets(indexed_triangle_set &its) { - TriangleMesh mesh(its_make_sphere(radius, fa)); - mesh.repair(); - return mesh; + for (stl_triangle_vertex_indices &face : its.indices) + std::swap(face[0], face[1]); } void its_merge(indexed_triangle_set &A, const indexed_triangle_set &B) @@ -1304,10 +1152,40 @@ std::vector its_split(const indexed_triangle_set &its) return its_split<>(its); } +// Number of disconnected patches (faces are connected if they share an edge, shared edge defined with 2 shared vertex indices). +bool its_number_of_patches(const indexed_triangle_set &its) +{ + return its_number_of_patches<>(its); +} +bool its_number_of_patches(const indexed_triangle_set &its, const std::vector &face_neighbors) +{ + return its_number_of_patches<>(ItsNeighborsWrapper{ its, face_neighbors }); +} + +// Same as its_number_of_patches(its) > 1, but faster. bool its_is_splittable(const indexed_triangle_set &its) { return its_is_splittable<>(its); } +bool its_is_splittable(const indexed_triangle_set &its, const std::vector &face_neighbors) +{ + return its_is_splittable<>(ItsNeighborsWrapper{ its, face_neighbors }); +} + +size_t its_num_open_edges(const std::vector &face_neighbors) +{ + size_t num_open_edges = 0; + for (Vec3i neighbors : face_neighbors) + for (int n : neighbors) + if (n < 0) + ++ num_open_edges; + return num_open_edges; +} + +size_t its_num_open_edges(const indexed_triangle_set &its) +{ + return its_num_open_edges(its_face_neighbors(its)); +} void VertexFaceIndex::create(const indexed_triangle_set &its) { @@ -1353,4 +1231,79 @@ std::vector its_face_normals(const indexed_triangle_set &its) return normals; } +#if BOOST_ENDIAN_LITTLE_BYTE +static inline void big_endian_reverse_quads(char*, size_t) {} +#else // BOOST_ENDIAN_LITTLE_BYTE +static inline void big_endian_reverse_quads(char *buf, size_t cnt) +{ + for (size_t i = 0; i < cnt; i += 4) { + std::swap(buf[i], buf[i+3]); + std::swap(buf[i+1], buf[i+2]); + } +} +#endif // BOOST_ENDIAN_LITTLE_BYTE + +bool its_write_stl_ascii(const char *file, const char *label, const std::vector &indices, const std::vector &vertices) +{ + FILE *fp = boost::nowide::fopen(file, "w"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "its_write_stl_ascii: Couldn't open " << file << " for writing"; + return false; + } + + fprintf(fp, "solid %s\n", label); + + for (const stl_triangle_vertex_indices face : indices) { + Vec3f vertex[3] = { vertices[face(0)], vertices[face(1)], vertices[face(2)] }; + Vec3f normal = (vertex[1] - vertex[0]).cross(vertex[2] - vertex[1]).normalized(); + fprintf(fp, " facet normal % .8E % .8E % .8E\n", normal(0), normal(1), normal(2)); + fprintf(fp, " outer loop\n"); + fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[0](0), vertex[0](1), vertex[0](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[1](0), vertex[1](1), vertex[1](2)); + fprintf(fp, " vertex % .8E % .8E % .8E\n", vertex[2](0), vertex[2](1), vertex[2](2)); + fprintf(fp, " endloop\n"); + fprintf(fp, " endfacet\n"); + } + + fprintf(fp, "endsolid %s\n", label); + fclose(fp); + return true; +} + +bool its_write_stl_binary(const char *file, const char *label, const std::vector &indices, const std::vector &vertices) +{ + FILE *fp = boost::nowide::fopen(file, "wb"); + if (fp == nullptr) { + BOOST_LOG_TRIVIAL(error) << "its_write_stl_binary: Couldn't open " << file << " for writing"; + return false; + } + + { + static constexpr const int header_size = 80; + std::vector header(header_size, 0); + if (int header_len = std::min((label == nullptr) ? 0 : int(strlen(label)), header_size); header_len > 0) + ::memcpy(header.data(), label, header_len); + ::fwrite(header.data(), header_size, 1, fp); + } + + uint32_t nfaces = indices.size(); + big_endian_reverse_quads(reinterpret_cast(&nfaces), 4); + ::fwrite(&nfaces, 4, 1, fp); + + stl_facet f; + f.extra[0] = 0; + f.extra[1] = 0; + for (const stl_triangle_vertex_indices face : indices) { + f.vertex[0] = vertices[face(0)]; + f.vertex[1] = vertices[face(1)]; + f.vertex[2] = vertices[face(2)]; + f.normal = (f.vertex[1] - f.vertex[0]).cross(f.vertex[2] - f.vertex[1]).normalized(); + big_endian_reverse_quads(reinterpret_cast(&f), 48); + fwrite(&f, 50, 1, fp); + } + + fclose(fp); + return true; +} + } // namespace Slic3r diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 60ab975c4c..d46fb4a8bc 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -15,25 +15,79 @@ namespace Slic3r { class TriangleMesh; class TriangleMeshSlicer; -typedef std::vector TriangleMeshPtrs; + +struct TriangleMeshStats { + // Mesh metrics. + uint32_t number_of_facets = 0; + stl_vertex max = stl_vertex::Zero(); + stl_vertex min = stl_vertex::Zero(); + stl_vertex size = stl_vertex::Zero(); + float volume = -1.f; + int number_of_parts = 0; + + // Mesh errors, remaining. + int open_edges = 0; + + // Mesh errors, fixed. + // How many edges were united by merging their end points with some other end points in epsilon neighborhood? + int edges_fixed = 0; + // How many degenerate faces were removed? + int degenerate_facets = 0; + // How many faces were removed during fixing? Includes degenerate_faces and disconnected faces. + int facets_removed = 0; + // New faces could only be created with stl_fill_holes() and we ditched stl_fill_holes(), because mostly it does more harm than good. + //int facets_added = 0; + // How many facets were revesed? Faces are reversed by admesh while it connects patches of triangles togeter and a flipped triangle is encountered. + // Also the facets are reversed when a negative volume is corrected by flipping all facets. + int facets_reversed = 0; + // Edges shared by two triangles, oriented incorrectly. + int backwards_edges = 0; + + void clear() { *this = TriangleMeshStats(); } + + TriangleMeshStats merge(const TriangleMeshStats &rhs) const { + if (this->number_of_facets == 0) + return rhs; + else if (rhs.number_of_facets == 0) + return *this; + else { + TriangleMeshStats out; + out.number_of_facets = this->number_of_facets + rhs.number_of_facets; + out.min = this->min.cwiseMin(rhs.min); + out.max = this->max.cwiseMax(rhs.max); + out.size = out.max - out.min; + out.number_of_parts = this->number_of_parts + rhs.number_of_parts; + out.open_edges = this->open_edges + rhs.open_edges; + out.volume = this->volume + rhs.volume; + out.edges_fixed = this->edges_fixed + rhs.edges_fixed; + out.degenerate_facets = this->degenerate_facets + rhs.degenerate_facets; + out.facets_removed = this->facets_removed + rhs.facets_removed; + out.facets_reversed = this->facets_reversed + rhs.facets_reversed; + out.backwards_edges = this->backwards_edges + rhs.backwards_edges; + return out; + } + } + + bool manifold() const { return open_edges == 0; } + bool repaired() const { return degenerate_facets > 0 || edges_fixed > 0 || facets_removed > 0 || facets_reversed > 0 || backwards_edges > 0; } +}; class TriangleMesh { public: - TriangleMesh() : repaired(false) {} - TriangleMesh(const Pointf3s &points, const std::vector &facets); + TriangleMesh() = default; + TriangleMesh(const std::vector &vertices, const std::vector &faces); + TriangleMesh(std::vector &&vertices, const std::vector &&faces); explicit TriangleMesh(const indexed_triangle_set &M); - void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } - bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } - bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } - bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } - void repair(bool update_shared_vertices = true); + explicit TriangleMesh(indexed_triangle_set &&M); + void clear() { this->its.clear(); this->m_stats.clear(); } + bool ReadSTLFile(const char* input_file, bool repair = true); + bool write_ascii(const char* output_file); + bool write_binary(const char* output_file); float volume(); - void check_topology(); - bool is_manifold() const { return this->stl.stats.connected_facets_3_edge == (int)this->stl.stats.number_of_facets; } void WriteOBJFile(const char* output_file) const; void scale(float factor); - void scale(const Vec3d &versor); + void scale(const Vec3f &versor); void translate(float x, float y, float z); void translate(const Vec3f &displacement); void rotate(float angle, const Axis &axis); @@ -41,15 +95,17 @@ public: void rotate_x(float angle) { this->rotate(angle, X); } void rotate_y(float angle) { this->rotate(angle, Y); } void rotate_z(float angle) { this->rotate(angle, Z); } - void mirror(const Axis &axis); + void mirror(const Axis axis); void mirror_x() { this->mirror(X); } void mirror_y() { this->mirror(Y); } void mirror_z() { this->mirror(Z); } void transform(const Transform3d& t, bool fix_left_handed = false); void transform(const Matrix3d& t, bool fix_left_handed = false); + // Flip triangles, negate volume. + void flip_triangles(); void align_to_origin(); void rotate(double angle, Point* center); - TriangleMeshPtrs split() const; + std::vector split() const; void merge(const TriangleMesh &mesh); ExPolygons horizontal_projection() const; // 2D convex hull of a 3D mesh projected into the Z=0 plane. @@ -58,37 +114,33 @@ public: // Returns the bbox of this TriangleMesh transformed by the given transformation BoundingBoxf3 transformed_bounding_box(const Transform3d &trafo) const; // Return the size of the mesh in coordinates. - Vec3d size() const { return stl.stats.size.cast(); } + Vec3d size() const { return m_stats.size.cast(); } /// Return the center of the related bounding box. Vec3d center() const { return this->bounding_box().center(); } // Returns the convex hull of this TriangleMesh TriangleMesh convex_hull_3d() const; // Slice this mesh at the provided Z levels and return the vector std::vector slice(const std::vector& z) const; - void reset_repair_stats(); - bool needed_repair() const; - void require_shared_vertices(); - bool has_shared_vertices() const { return ! this->its.vertices.empty(); } - size_t facets_count() const { return this->stl.stats.number_of_facets; } + size_t facets_count() const { assert(m_stats.number_of_facets == this->its.indices.size()); return m_stats.number_of_facets; } bool empty() const { return this->facets_count() == 0; } - bool is_splittable() const; + bool repaired() const; + bool is_splittable() const; // Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation. size_t memsize() const; - // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. - size_t release_optional(); - // Restore optional data possibly released by release_optional(). - void restore_optional(); - const stl_stats& stats() const { return this->stl.stats; } + // Used by the Undo / Redo stack, legacy interface. As of now there is nothing cached at TriangleMesh, + // but we may decide to cache some data in the future (for example normals), thus we keep the interface in place. + // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. + size_t release_optional() { return 0; } + // Restore optional data possibly released by release_optional(). + void restore_optional() {} + + const TriangleMeshStats& stats() const { return m_stats; } indexed_triangle_set its; - bool repaired; - -//private: - stl_file stl; private: - std::deque find_unvisited_neighbors(std::vector &facet_visited) const; + TriangleMeshStats m_stats; }; // Index of face indices incident with a vertex index. @@ -148,8 +200,18 @@ bool its_store_triangle(const indexed_triangle_set &its, const char *obj_filenam bool its_store_triangles(const indexed_triangle_set &its, const char *obj_filename, const std::vector& triangles); std::vector its_split(const indexed_triangle_set &its); +std::vector its_split(const indexed_triangle_set &its, std::vector &face_neighbors); +// Number of disconnected patches (faces are connected if they share an edge, shared edge defined with 2 shared vertex indices). +bool its_number_of_patches(const indexed_triangle_set &its); +bool its_number_of_patches(const indexed_triangle_set &its, const std::vector &face_neighbors); +// Same as its_number_of_patches(its) > 1, but faster. bool its_is_splittable(const indexed_triangle_set &its); +bool its_is_splittable(const indexed_triangle_set &its, const std::vector &face_neighbors); + +// Calculate number of unconnected face edges. There should be no unconnected edge in a manifold mesh. +size_t its_num_open_edges(const indexed_triangle_set &its); +size_t its_num_open_edges(const std::vector &face_neighbors); // Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors. void its_shrink_to_fit(indexed_triangle_set &its); @@ -217,13 +279,23 @@ inline Vec3f its_face_normal(const indexed_triangle_set &its, const int face_idx { return its_face_normal(its, its.indices[face_idx]); } indexed_triangle_set its_make_cube(double x, double y, double z); -TriangleMesh make_cube(double x, double y, double z); +indexed_triangle_set its_make_prism(float width, float length, float height); indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360)); -TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360)); indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360)); -TriangleMesh make_cone(double r, double h, double fa=(2*PI/360)); +indexed_triangle_set its_make_pyramid(float base, float height); indexed_triangle_set its_make_sphere(double radius, double fa); -TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); + +inline TriangleMesh make_cube(double x, double y, double z) { return TriangleMesh(its_make_cube(x, y, z)); } +inline TriangleMesh make_prism(float width, float length, float height) { return TriangleMesh(its_make_prism(width, length, height)); } +inline TriangleMesh make_cylinder(double r, double h, double fa=(2*PI/360)) { return TriangleMesh{its_make_cylinder(r, h, fa)}; } +inline TriangleMesh make_cone(double r, double h, double fa=(2*PI/360)) { return TriangleMesh(its_make_cone(r, h, fa)); } +inline TriangleMesh make_pyramid(float base, float height) { return TriangleMesh(its_make_pyramid(base, height)); } +inline TriangleMesh make_sphere(double rho, double fa=(2*PI/360)) { return TriangleMesh(its_make_sphere(rho, fa)); } + +bool its_write_stl_ascii(const char *file, const char *label, const std::vector &indices, const std::vector &vertices); +inline bool its_write_stl_ascii(const char *file, const char *label, const indexed_triangle_set &its) { return its_write_stl_ascii(file, label, its.indices, its.vertices); } +bool its_write_stl_binary(const char *file, const char *label, const std::vector &indices, const std::vector &vertices); +inline bool its_write_stl_binary(const char *file, const char *label, const indexed_triangle_set &its) { return its_write_stl_binary(file, label, its.indices, its.vertices); } inline BoundingBoxf3 bounding_box(const TriangleMesh &m) { return m.bounding_box(); } inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its) @@ -248,18 +320,12 @@ inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its) namespace cereal { template struct specialize {}; template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { - stl_file &stl = mesh.stl; - stl.stats.type = inmemory; - archive(stl.stats.number_of_facets, stl.stats.original_num_facets); - stl_allocate(&stl); - archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); - stl_get_size(&stl); - mesh.repair(); + archive.loadBinary(reinterpret_cast(const_cast(&mesh.stats())), sizeof(Slic3r::TriangleMeshStats)); + archive(mesh.its.indices, mesh.its.vertices); } template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { - const stl_file& stl = mesh.stl; - archive(stl.stats.number_of_facets, stl.stats.original_num_facets); - archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + archive.saveBinary(reinterpret_cast(&mesh.stats()), sizeof(Slic3r::TriangleMeshStats)); + archive(mesh.its.indices, mesh.its.vertices); } } diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index aa2763968d..dd11420bbd 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1967,7 +1967,8 @@ static void triangulate_slice( int num_original_vertices, // Z height of the slice. float z, - bool triangulate) + bool triangulate, + bool normals_down) { sort_remove_duplicates(slice_vertices); @@ -2013,7 +2014,7 @@ static void triangulate_slice( if (triangulate) { size_t idx_vertex_new_first = its.vertices.size(); - Pointf3s triangles = triangulate_expolygons_3d(make_expolygons_simple(lines), z, true); + Pointf3s triangles = triangulate_expolygons_3d(make_expolygons_simple(lines), z, normals_down); for (size_t i = 0; i < triangles.size(); ) { stl_triangle_vertex_indices facet; for (size_t j = 0; j < 3; ++ j) { @@ -2049,6 +2050,33 @@ static void triangulate_slice( // its_remove_degenerate_faces(its); } +void project_mesh( + const indexed_triangle_set &mesh, + const Transform3d &trafo, + Polygons *out_top, + Polygons *out_bottom, + std::function throw_on_cancel) +{ + std::vector top, bottom; + std::vector zs { -1e10, 1e10 }; + slice_mesh_slabs(mesh, zs, trafo, out_top ? &top : nullptr, out_bottom ? &bottom : nullptr, throw_on_cancel); + if (out_top) + *out_top = std::move(top.front()); + if (out_bottom) + *out_bottom = std::move(bottom.back()); +} + +Polygons project_mesh( + const indexed_triangle_set &mesh, + const Transform3d &trafo, + std::function throw_on_cancel) +{ + std::vector top, bottom; + std::vector zs { -1e10, 1e10 }; + slice_mesh_slabs(mesh, zs, trafo, &top, &bottom, throw_on_cancel); + return union_(top.front(), bottom.back()); +} + void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *upper, indexed_triangle_set *lower, bool triangulate_caps) { assert(upper || lower); @@ -2196,10 +2224,10 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u } if (upper != nullptr) - triangulate_slice(*upper, upper_lines, upper_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps); + triangulate_slice(*upper, upper_lines, upper_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps, NORMALS_DOWN); if (lower != nullptr) - triangulate_slice(*lower, lower_lines, lower_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps); + triangulate_slice(*lower, lower_lines, lower_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps, NORMALS_UP); } } diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index 7ea7ac3a91..5e08b58e77 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -98,7 +98,21 @@ void slice_mesh_slabs( std::vector *out_bottom, std::function throw_on_cancel); -void cut_mesh( +// Project mesh upwards pointing surfaces / downwards pointing surfaces into 2D polygons. +void project_mesh( + const indexed_triangle_set &mesh, + const Transform3d &trafo, + Polygons *out_top, + Polygons *out_bottom, + std::function throw_on_cancel); + +// Project mesh into 2D polygons. +Polygons project_mesh( + const indexed_triangle_set &mesh, + const Transform3d &trafo, + std::function throw_on_cancel); + +void cut_mesh( const indexed_triangle_set &mesh, float z, indexed_triangle_set *upper, diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index c5dbdac9cd..d63ce1f635 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -906,6 +906,7 @@ unsigned get_current_pid() #endif } +//FIXME this has potentially O(n^2) time complexity! std::string xml_escape(std::string text, bool is_marked/* = false*/) { std::string::size_type pos = 0; diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index f693143c42..dfc10658dd 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -313,7 +313,6 @@ void GLVolume::SinkingContours::update() m_shift = Vec3d::Zero(); const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh(); - assert(mesh.has_shared_vertices()); m_model.reset(); GUI::GLModel::InitializationData init_data; @@ -519,7 +518,7 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const { - return (m_convex_hull && m_convex_hull->facets_count() > 0) ? + return (m_convex_hull && ! m_convex_hull->empty()) ? m_convex_hull->transformed_bounding_box(trafo) : bounding_box().transformed(trafo); } @@ -719,21 +718,20 @@ int GLVolumeCollection::load_wipe_tower_preview( float min_width = 30.f; // We'll now create the box with jagged edge. y-coordinates of the pre-generated model // are shifted so that the front edge has y=0 and centerline of the back edge has y=depth: - Pointf3s points; - std::vector facets; float out_points_idx[][3] = { { 0, -depth, 0 }, { 0, 0, 0 }, { 38.453f, 0, 0 }, { 61.547f, 0, 0 }, { 100.0f, 0, 0 }, { 100.0f, -depth, 0 }, { 55.7735f, -10.0f, 0 }, { 44.2265f, 10.0f, 0 }, { 38.453f, 0, 1 }, { 0, 0, 1 }, { 0, -depth, 1 }, { 100.0f, -depth, 1 }, { 100.0f, 0, 1 }, { 61.547f, 0, 1 }, { 55.7735f, -10.0f, 1 }, { 44.2265f, 10.0f, 1 } }; - int out_facets_idx[][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 }, - {8, 10, 14}, {3, 12, 4}, {3, 13, 12}, {6, 13, 3}, {6, 14, 13}, {7, 14, 6}, {7, 15, 14}, {2, 15, 7}, {2, 8, 15}, {1, 8, 2}, {1, 9, 8}, - {0, 9, 1}, {0, 10, 9}, {5, 10, 0}, {5, 11, 10}, {4, 11, 5}, {4, 12, 11} }; + static constexpr const int out_facets_idx[][3] = { + { 0, 1, 2 }, { 3, 4, 5 }, { 6, 5, 0 }, { 3, 5, 6 }, { 6, 2, 7 }, { 6, 0, 2 }, { 8, 9, 10 }, { 11, 12, 13 }, { 10, 11, 14 }, { 14, 11, 13 }, { 15, 8, 14 }, + { 8, 10, 14 }, { 3, 12, 4 }, { 3, 13, 12 }, { 6, 13, 3 }, { 6, 14, 13 }, { 7, 14, 6 }, { 7, 15, 14 }, { 2, 15, 7 }, { 2, 8, 15 }, { 1, 8, 2 }, { 1, 9, 8 }, + { 0, 9, 1 }, { 0, 10, 9 }, { 5, 10, 0 }, { 5, 11, 10 }, { 4, 11, 5 }, { 4, 12, 11 } }; + indexed_triangle_set its; for (int i = 0; i < 16; ++i) - points.emplace_back(out_points_idx[i][0] / (100.f / min_width), - out_points_idx[i][1] + depth, out_points_idx[i][2]); - for (int i = 0; i < 28; ++i) - facets.emplace_back(out_facets_idx[i][0], - out_facets_idx[i][1], - out_facets_idx[i][2]); - TriangleMesh tooth_mesh(points, facets); + its.vertices.emplace_back(out_points_idx[i][0] / (100.f / min_width), + out_points_idx[i][1] + depth, out_points_idx[i][2]); + its.indices.reserve(28); + for (const int *face : out_facets_idx) + its.indices.emplace_back(face); + TriangleMesh tooth_mesh(std::move(its)); // We have the mesh ready. It has one tooth and width of min_width. We will now // append several of these together until we are close to the required width @@ -744,7 +742,7 @@ int GLVolumeCollection::load_wipe_tower_preview( tooth_mesh.translate(min_width, 0.f, 0.f); } - mesh.scale(Vec3d(width / (n * min_width), 1.f, height)); // Scaling to proper width + mesh.scale(Vec3f(width / (n * min_width), 1.f, height)); // Scaling to proper width } else mesh = make_cube(width, depth, height); @@ -753,7 +751,6 @@ int GLVolumeCollection::load_wipe_tower_preview( TriangleMesh brim_mesh = make_cube(width + 2.f * brim_width, depth + 2.f * brim_width, 0.2f); brim_mesh.translate(-brim_width, -brim_width, 0.f); mesh.merge(brim_mesh); - mesh.repair(); volumes.emplace_back(new GLVolume(color)); GLVolume& v = *volumes.back(); diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 298fb21b0b..fb27aced50 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -160,7 +160,6 @@ bool GLModel::init_from_file(const std::string& filename) } TriangleMesh mesh = model.mesh(); - mesh.require_shared_vertices(); init_from(mesh.its, mesh.bounding_box()); m_filename = filename; diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index da4a842d49..35e5bb83e2 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -392,9 +392,9 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx / // Create tooltip string, if there are errors wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors) + ":\n"; - const stl_stats& stats = vol_idx == -1 ? - (*m_objects)[obj_idx]->get_object_stl_stats() : - (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats(); + const TriangleMeshStats& stats = vol_idx == -1 ? + (*m_objects)[obj_idx]->get_object_stl_stats() : + (*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stats(); if (stats.degenerate_facets > 0) tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d degenerate facet", "%1$d degenerate facets", stats.degenerate_facets), stats.degenerate_facets) + "\n"; @@ -402,8 +402,6 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx / tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d edge fixed", "%1$d edges fixed", stats.edges_fixed), stats.edges_fixed) + "\n"; if (stats.facets_removed > 0) tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet removed", "%1$d facets removed", stats.facets_removed), stats.facets_removed) + "\n"; - if (stats.facets_added > 0) - tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet added", "%1$d facets added", stats.facets_added), stats.facets_added) + "\n"; if (stats.facets_reversed > 0) tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", stats.facets_reversed), stats.facets_reversed) + "\n"; if (stats.backwards_edges > 0) @@ -1535,7 +1533,6 @@ void ObjectList::load_modifier(ModelObject& model_object, std::vectorname = boost::filesystem::path(input_file).filename().string(); @@ -1558,27 +1555,24 @@ void ObjectList::load_modifier(ModelObject& model_object, std::vectorcanvas3D()->get_size_proportional_to_max_bed_size(0.1); + indexed_triangle_set mesh; if (type_name == "Box") // Sitting on the print bed, left front front corner at (0, 0). - mesh = make_cube(side, side, side); + mesh = its_make_cube(side, side, side); else if (type_name == "Cylinder") // Centered around 0, sitting on the print bed. // The cylinder has the same volume as the box above. - mesh = make_cylinder(0.564 * side, side); + mesh = its_make_cylinder(0.564 * side, side); else if (type_name == "Sphere") // Centered around 0, half the sphere below the print bed, half above. // The sphere has the same volume as the box above. - mesh = make_sphere(0.62 * side, PI / 18); + mesh = its_make_sphere(0.62 * side, PI / 18); else if (type_name == "Slab") // Sitting on the print bed, left front front corner at (0, 0). - mesh = make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5); - mesh.repair(); - - return mesh; + mesh = its_make_cube(bb.size().x() * 1.5, bb.size().y() * 1.5, bb.size().z() * 0.5); + return TriangleMesh(mesh); } void ObjectList::load_generic_subobject(const std::string& type_name, const ModelVolumeType type) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index e4cbd77d41..c8142ba347 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -282,10 +282,8 @@ void GLGizmoCut::update_contours() if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || m_cut_contours.instance_idx != instance_idx) { m_cut_contours.cut_z = m_cut_z; - if (m_cut_contours.object_id != model_object->id()) { + if (m_cut_contours.object_id != model_object->id()) m_cut_contours.mesh = model_object->raw_mesh(); - m_cut_contours.mesh.repair(); - } m_cut_contours.position = box.center(); m_cut_contours.shift = Vec3d::Zero(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index a2c4910e34..d4ee885b3f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -313,9 +313,8 @@ void GLGizmoSimplify::process() } void GLGizmoSimplify::set_its(indexed_triangle_set &its) { - auto tm = std::make_unique(its); - tm->repair(); - m_volume->set_mesh(std::move(tm)); + m_volume->set_mesh(its); + m_volume->calculate_convex_hull(); m_volume->set_new_unique_id(); m_volume->get_object()->invalidate_bounding_box(); m_need_reload = true; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index b22e72be9e..cf4b18a86b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -270,11 +270,10 @@ void HollowedMesh::on_update() m_drainholes = print_object->model_object()->sla_drain_holes; m_old_hollowing_timestamp = timestamp; - const indexed_triangle_set &interior = print_object->hollowed_interior_mesh(); + indexed_triangle_set interior = print_object->hollowed_interior_mesh(); if (!interior.empty()) { - m_hollowed_interior_transformed = std::make_unique(interior); - m_hollowed_interior_transformed->repaired = false; - m_hollowed_interior_transformed->repair(true); + its_flip_triangles(interior); + m_hollowed_interior_transformed = std::make_unique(std::move(interior)); m_hollowed_interior_transformed->transform(trafo_inv); } } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6d5fbf11c7..89b6a6bea9 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1740,7 +1740,6 @@ void MainFrame::repair_stl() Slic3r::TriangleMesh tmesh; tmesh.ReadSTLFile(input_file.ToUTF8().data()); - tmesh.repair(); tmesh.WriteOBJFile(output_file.ToUTF8().data()); Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair")); } diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 75232c9301..576728b247 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -91,11 +91,9 @@ void MeshClipper::recalculate_triangles() MeshSlicingParams slicing_params; slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); - assert(m_mesh->has_shared_vertices()); ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params)); if (m_negative_mesh && !m_negative_mesh->empty()) { - assert(m_negative_mesh->has_shared_vertices()); ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params)); expolys = diff_ex(expolys, neg_expolys); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 9dd08b6fe1..126e889165 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1135,7 +1135,7 @@ void Sidebar::show_info_sizer() static_cast(model_object->facets_count()), stats.number_of_parts)); int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_added + stats.facets_reversed + stats.backwards_edges; + stats.facets_reversed + stats.backwards_edges; if (errors > 0) { wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors); p->object_info->info_manifold->SetLabel(tooltip); @@ -1147,8 +1147,6 @@ void Sidebar::show_info_sizer() tooltip += format_wxstr(_L_PLURAL("%1$d edge fixed", "%1$d edges fixed", stats.edges_fixed), stats.edges_fixed) + ", "; if (stats.facets_removed > 0) tooltip += format_wxstr(_L_PLURAL("%1$d facet removed", "%1$d facets removed", stats.facets_removed), stats.facets_removed) + ", "; - if (stats.facets_added > 0) - tooltip += format_wxstr(_L_PLURAL("%1$d facet added", "%1$d facets added", stats.facets_added), stats.facets_added) + ", "; if (stats.facets_reversed > 0) tooltip += format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", stats.facets_reversed), stats.facets_reversed) + ", "; if (stats.backwards_edges > 0) @@ -2544,16 +2542,14 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& mode if (max_ratio > 10000) { // the size of the object is too big -> this could lead to overflow when moving to clipper coordinates, // so scale down the mesh - double inv = 1. / max_ratio; - object->scale_mesh_after_creation(inv * Vec3d::Ones()); + object->scale_mesh_after_creation(1. / max_ratio); object->origin_translation = Vec3d::Zero(); object->center_around_origin(); scaled_down = true; break; } else if (max_ratio > 5) { - const Vec3d inverse = 1.0 / max_ratio * Vec3d::Ones(); - instance->set_scaling_factor(inverse.cwiseProduct(instance->get_scaling_factor())); + instance->set_scaling_factor(instance->get_scaling_factor() / max_ratio); scaled_down = true; } } @@ -5587,11 +5583,9 @@ void Plater::export_stl(bool extended, bool selection_only) for (const ModelVolume *v : mo->volumes) if (v->is_model_part()) { TriangleMesh vol_mesh(v->mesh()); - vol_mesh.repair(); vol_mesh.transform(v->get_matrix(), true); mesh.merge(vol_mesh); } - mesh.repair(); if (instances) { TriangleMesh vols_mesh(mesh); mesh = TriangleMesh(); @@ -5601,7 +5595,6 @@ void Plater::export_stl(bool extended, bool selection_only) mesh.merge(m); } } - mesh.repair(); return mesh; }; diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 931d2f4493..30c81f6f75 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -402,6 +402,7 @@ bool fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx, wxPro } for (size_t i = 0; i < volumes.size(); ++ i) { volumes[i]->set_mesh(std::move(meshes_repaired[i])); + volumes[i]->calculate_convex_hull(); volumes[i]->set_new_unique_id(); } model_object.invalidate_bounding_box(); diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index f5424dfd90..32e31c264c 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -58,12 +58,13 @@ TriangleMesh mesh(TestMesh m) break; case TestMesh::cube_with_concave_hole: mesh = TriangleMesh( - { {-10,-10,-5}, {-10,-10,5}, {-10,10,-5}, {-10,10,5}, {10,-10,-5}, {10,-10,5}, {-5,-5,-5}, {5,-5,-5}, {5,5,-5}, {5,10,-5}, {-5,5,-5}, Vec3d(3.06161699911402e-16,5,-5), {5,0,-5}, {0,0,-5}, {10,5,-5}, {5,10,5}, {-5,-5,5}, {5,0,5}, {5,-5,5}, {-5,5,5}, {10,5,5}, {5,5,5}, Vec3d(3.06161699911402e-16,5,5), {0,0,5} }, + { {-10,-10,-5}, {-10,-10,5}, {-10,10,-5}, {-10,10,5}, {10,-10,-5}, {10,-10,5}, {-5,-5,-5}, {5,-5,-5}, {5,5,-5}, {5,10,-5}, {-5,5,-5}, Vec3f(3.06161699911402e-16f,5,-5), + {5,0,-5}, {0,0,-5}, {10,5,-5}, {5,10,5}, {-5,-5,5}, {5,0,5}, {5,-5,5}, {-5,5,5}, {10,5,5}, {5,5,5}, Vec3f(3.06161699911402e-16f,5,5), {0,0,5} }, { {0,1,2}, {2,1,3}, {1,0,4}, {5,1,4}, {6,7,4}, {8,2,9}, {10,2,11}, {11,12,13}, {0,2,10}, {0,10,6}, {0,6,4}, {11,2,8}, {4,7,12}, {4,12,8}, {12,11,8}, {14,4,8}, {2,3,9}, {9,3,15}, {16,1,5}, {17,18,5}, {19,3,16}, {20,21,5}, {18,16,5}, {3,1,16}, {22,3,19}, {21,3,22}, {21,17,5}, {21,22,17}, {21,15,3}, {23,17,22}, {5,4,14}, {20,5,14}, {20,14,21}, {21,14,8}, {9,15,21}, {8,9,21}, {10,19,16}, {6,10,16}, {11,22,19}, {10,11,19}, {13,23,11}, {11,23,22}, {23,13,12}, {17,23,12}, {17,12,18}, {18,12,7}, {18,7,16}, {16,7,6} }); break; case TestMesh::V: mesh = TriangleMesh( - { {-14,0,20}, {-14,15,20}, {0,0,0}, {0,15,0}, {-4,0,20}, {-4,15,20}, {5,0,7.14286}, {10,0,0}, {24,0,20}, {14,0,20}, {10,15,0}, {5,15,7.14286}, {14,15,20}, {24,15,20} }, + { {-14,0,20}, {-14,15,20}, {0,0,0}, {0,15,0}, {-4,0,20}, {-4,15,20}, {5,0,7.14286f}, {10,0,0}, {24,0,20}, {14,0,20}, {10,15,0}, {5,15,7.14286f}, {14,15,20}, {24,15,20} }, { {0,1,2}, {2,1,3}, {1,0,4}, {5,1,4}, {4,0,2}, {6,4,2}, {7,6,2}, {8,9,7}, {9,6,7}, {2,3,7}, {7,3,10}, {1,5,3}, {3,5,11}, {11,12,13}, {11,13,3}, {3,13,10}, {5,4,6}, {11,5,6}, {6,9,11}, {11,9,12}, {12,9,8}, {13,12,8}, {8,7,10}, {13,8,10} }); break; case TestMesh::L: @@ -88,7 +89,7 @@ TriangleMesh mesh(TestMesh m) break; case TestMesh::ipadstand: mesh = TriangleMesh( - { Vec3d(17.4344673156738,-2.69879599481136e-16,9.5), {14.2814798355103,10,9.5}, {0,0,9.5}, {31.7159481048584,10,9.5}, Vec3d(62.2344741821289,2.06667568800577e-16,20), {31.7159481048584,10,20}, Vec3d(17.4344673156738,-2.69879599481136e-16,20), {62.2344741821289,10,20}, {98.2079696655273,10,0}, Vec3d(98.2079696655273,8.56525380796383e-16,10), {98.2079696655273,0,0}, {98.2079696655273,10,20}, {98.2079696655273,0,20}, Vec3d(81.6609649658203,-4.39753856997999e-16,10), {90.0549850463867,10,10}, {78.5079803466797,10,10}, Vec3d(93.2079696655273,8.56525380796383e-16,10), {14.2814798355103,10,20}, {0,0,20}, Vec3d(87.4344711303711,2.81343962782118e-15,20), {84.2814788818359,10,20}, {0,10,20}, {0,0,0}, {0,10,0}, Vec3d(62.2344741821289,2.06667568800577e-16,30), {66.9609756469727,10,30}, {62.2344741821289,10,30}, Vec3d(70.1139602661133,8.5525763717214e-16,30), {67.7053375244141,10,28.7107200622559}, Vec3d(71.6787109375,1.24046736339707e-15,27.2897701263428) }, + { Vec3f(17.4344673156738,-2.69879599481136e-16,9.5), {14.2814798355103,10,9.5}, {0,0,9.5}, {31.7159481048584,10,9.5}, Vec3f(62.2344741821289,2.06667568800577e-16,20), {31.7159481048584,10,20}, Vec3f(17.4344673156738,-2.69879599481136e-16,20), {62.2344741821289,10,20}, {98.2079696655273,10,0}, Vec3f(98.2079696655273,8.56525380796383e-16,10), {98.2079696655273,0,0}, {98.2079696655273,10,20}, {98.2079696655273,0,20}, Vec3f(81.6609649658203,-4.39753856997999e-16,10), {90.0549850463867,10,10}, {78.5079803466797,10,10}, Vec3f(93.2079696655273,8.56525380796383e-16,10), {14.2814798355103,10,20}, {0,0,20}, Vec3f(87.4344711303711,2.81343962782118e-15,20), {84.2814788818359,10,20}, {0,10,20}, {0,0,0}, {0,10,0}, Vec3f(62.2344741821289,2.06667568800577e-16,30), {66.9609756469727,10,30}, {62.2344741821289,10,30}, Vec3f(70.1139602661133,8.5525763717214e-16,30), {67.7053375244141,10,28.7107200622559}, Vec3f(71.6787109375,1.24046736339707e-15,27.2897701263428) }, { {0,1,2}, {1,0,3}, {4,5,6}, {5,4,7}, {8,9,10}, {9,11,12}, {11,9,8}, {13,14,15}, {14,13,16}, {17,2,1}, {2,17,18}, {19,11,20}, {11,19,12}, {17,21,18}, {21,2,18}, {2,21,22}, {22,21,23}, {8,22,23}, {22,8,10}, {24,25,26}, {25,24,27}, {23,1,8}, {1,23,21}, {1,21,17}, {5,15,3}, {15,5,7}, {15,7,28}, {28,7,26}, {28,26,25}, {8,14,11}, {14,8,3}, {3,8,1}, {14,3,15}, {11,14,20}, {26,4,24}, {4,26,7}, {12,16,9}, {16,12,19}, {29,4,13}, {4,29,24}, {24,29,27}, {9,22,10}, {22,9,0}, {0,9,16}, {0,16,13}, {0,13,6}, {6,13,4}, {2,22,0}, {19,14,16}, {14,19,20}, {15,29,13}, {29,25,27}, {25,29,15}, {25,15,28}, {6,3,0}, {3,6,5} }); break; case TestMesh::A: @@ -98,7 +99,37 @@ TriangleMesh mesh(TestMesh m) break; case TestMesh::gt2_teeth: mesh = TriangleMesh( - { {15.8899993896484,19.444055557251,2.67489433288574}, {15.9129991531372,19.1590557098389,2.67489433288574}, {15.9039993286133,19.1500549316406,2.67489433288574}, {15.9489994049072,19.2490558624268,2.67489433288574}, {15.9579992294312,19.3570556640625,2.67489433288574}, {15.8819999694824,18.690055847168,2.67489433288574}, {15.8319997787476,17.7460556030273,2.67489433288574}, {15.8489999771118,18.819055557251,2.67489433288574}, {15.8589992523193,17.7190551757812,2.67489433288574}, {15.8769998550415,19.0490550994873,2.67489433288574}, {15.7529993057251,17.8080558776855,2.67489433288574}, {15.7869997024536,19.5010547637939,2.67489433288574}, {14.0329990386963,18.7170543670654,2.67489433288574}, {13.9599990844727,18.7460556030273,2.67489433288574}, {13.9869995117188,20.2840557098389,2.67489433288574}, {14.2029991149902,20.149055480957,2.67489433288574}, {14.1939992904663,19.9560546875,2.67489433288574}, {14.1939992904663,20.1670551300049,2.67489433288574}, {14.2119998931885,20.0590553283691,2.67489433288574}, {12.1899995803833,19.1840553283691,2.67489433288574}, {12.096999168396,19.1950550079346,2.67489433288574}, {12.1099996566772,20.6690559387207,2.67489433288574}, {11.382999420166,19.9750556945801,2.67489433288574}, {11.2599992752075,19.2490558624268,2.67489433288574}, {11.2369995117188,19.9320545196533,2.67489433288574}, {11.5349998474121,20.0640544891357,2.67489433288574}, {11.6259994506836,20.1550559997559,2.67489433288574}, {11.6829986572266,20.2390556335449,2.67489433288574}, {11.7369995117188,20.3570556640625,2.67489433288574}, {11.8449993133545,20.645055770874,2.67489433288574}, {11.7729988098145,20.4640560150146,2.67489433288574}, {11.7799987792969,20.5370559692383,9.41389465332031}, {11.7639999389648,20.4470558166504,2.67489433288574}, {11.9559993743896,20.6810550689697,2.67489433288574}, {12.3079996109009,20.6020545959473,2.67489433288574}, {12.1959991455078,19.1860542297363,2.67489433288574}, {12.2059993743896,20.6540546417236,2.67489433288574}, {12.3489990234375,20.3740558624268,2.67489433288574}, {12.3579998016357,20.2750549316406,2.67489433288574}, {12.3669996261597,20.266056060791,2.67489433288574}, {12.3849992752075,20.1670551300049,2.67489433288574}, {12.4269990921021,20.0680541992188,2.67489433288574}, {12.5029993057251,19.9540557861328,2.67489433288574}, {12.6169996261597,19.8550548553467,2.67489433288574}, {12.7449989318848,19.7800559997559,2.67489433288574}, {12.7629995346069,19.7800559997559,2.67489433288574}, {12.8799991607666,19.7350559234619,2.67489433288574}, {13.0369997024536,19.7250556945801,2.67489433288574}, {13.0149993896484,19.0340557098389,2.67489433288574}, {11.1699991226196,19.2580547332764,2.67489433288574}, {11.0959987640381,19.2580547332764,2.67489433288574}, {11.1209993362427,19.9230556488037,2.67489433288574}, {13.0599994659424,19.024055480957,2.67489433288574}, {14.9049997329712,18.3170547485352,2.67489433288574}, {14.8779993057251,18.3400554656982,2.67489433288574}, {14.8779993057251,19.149055480957,2.67489433288574}, {13.3039989471436,19.77805519104,2.67489433288574}, {13.1589994430542,18.9890556335449,2.67489433288574}, {13.1559991836548,19.7350559234619,2.67489433288574}, {13.4269990921021,19.8600559234619,2.67489433288574}, {13.5339994430542,19.9700546264648,2.67389440536499}, {13.6359996795654,20.1220550537109,2.67489433288574}, {13.6359996795654,20.1400547027588,2.67489433288574}, {13.6719989776611,20.2210559844971,2.67489433288574}, {13.6899995803833,20.2300548553467,2.67489433288574}, {13.7509994506836,20.3010559082031,2.67489433288574}, {13.8539991378784,20.3180541992188,2.67489433288574}, {14.8329992294312,18.3580551147461,2.67489433288574}, {14.1849994659424,19.8530559539795,2.67489433288574}, {14.0769996643066,18.7000541687012,2.67489433288574}, {14.1099996566772,20.2400550842285,2.67489433288574}, {14.2009992599487,19.6230545043945,2.67489433288574}, {14.2729997634888,19.4670543670654,2.67489433288574}, {14.3379993438721,19.3790550231934,2.67489433288574}, {14.4549999237061,19.2770557403564,2.67489433288574}, {14.5899991989136,19.2040557861328,2.67489433288574}, {14.6079998016357,19.2040557861328,2.67489433288574}, {14.7209997177124,19.1600551605225,2.67489433288574}, {15.1379995346069,19.210054397583,2.67489433288574}, {14.9949998855591,18.2680549621582,2.67489433288574}, {15.0029993057251,19.1580543518066,2.67489433288574}, {15.2369995117188,19.2760543823242,2.67489433288574}, {15.3779993057251,19.4060554504395,2.67489433288574}, {15.4539995193481,19.520055770874,2.67489433288574}, {15.471999168396,19.52805519104,2.67489433288574}, {15.5449991226196,19.5830554962158,2.67489433288574}, {15.6529998779297,19.573055267334,2.67489433288574}, {15.7059993743896,17.8360557556152,2.67489433288574}, {15.9449996948242,18.5560550689697,2.67489433288574}, {15.8589992523193,18.9380550384521,2.67489433288574}, {14.9589996337891,18.2950553894043,2.67489433288574}, {15.7779998779297,19.5100555419922,2.67489433288574}, {14.0049991607666,20.2750549316406,2.67489433288574}, {12.3489990234375,20.5000553131104,2.67489433288574}, {13.0689992904663,19.0150547027588,2.67489433288574}, {13.0999994277954,19.0100555419922,2.67489433288574}, {15.9489994049072,19.3670558929443,9.41489505767822}, {15.9489994049072,19.2490558624268,9.41489505767822}, {15.75,17.8080558776855,9.41489505767822}, {15.6639995574951,19.5710544586182,9.41489505767822}, {15.5709991455078,17.9260559082031,9.41489505767822}, {15.8769998550415,18.690055847168,9.41489505767822}, {15.8499994277954,18.8170547485352,9.41489505767822}, {15.9459991455078,18.5520553588867,9.41489505767822}, {15.914999961853,17.6890544891357,9.41489505767822}, {15.3999996185303,19.4290542602539,9.41489505767822}, {15.3099994659424,19.339054107666,9.41489505767822}, {15.3729991912842,18.0440559387207,9.41489505767822}, {15.4579992294312,19.5170555114746,9.41489505767822}, {15.5469999313354,19.5820541381836,9.41489505767822}, {13.2309989929199,19.7610549926758,9.41489505767822}, {13.168999671936,19.7360553741455,9.41489505767822}, {13.096999168396,19.0140552520752,9.41489505767822}, {13.1999988555908,18.9870548248291,9.41489505767822}, {15.1399993896484,19.2080554962158,9.41489505767822}, {15.0159997940063,19.1600551605225,9.41489505767822}, {14.9859991073608,18.2770557403564,9.41489505767822}, {15.1749992370605,18.1690559387207,9.41489505767822}, {15.9039993286133,19.1320552825928,9.41489505767822}, {15.8949995040894,19.4460544586182,9.41489505767822}, {15.8769998550415,19.0420551300049,9.41489505767822}, {12.2169990539551,20.6500549316406,9.41489505767822}, {11.9379997253418,20.6810550689697,9.41489505767822}, {11.8629989624023,19.2130546569824,9.41489505767822}, {12.096999168396,19.1950550079346,9.41489505767822}, {14.1669998168945,18.6640548706055,9.41489505767822}, {14.1039991378784,20.2460556030273,9.41489505767822}, {13.9849996566772,18.7360553741455,9.41489505767822}, {14.7349996566772,19.1590557098389,9.41489505767822}, {14.5849990844727,19.2050552368164,9.41489505767822}, {14.5719995498657,18.4850559234619,9.41489505767822}, {14.1939992904663,19.6760559082031,9.41489505767822}, {14.1849994659424,19.9330558776855,9.41489505767822}, {14.1759996414185,18.6640548706055,9.41489505767822}, {14.261999130249,19.4890556335449,9.41489505767822}, {14.3539991378784,19.3610553741455,9.41489505767822}, {14.3559989929199,18.5830554962158,9.41489505767822}, {11.6039991378784,20.1250553131104,9.41489505767822}, {11.5209999084473,20.0520553588867,9.41489505767822}, {11.4209995269775,19.2480545043945,9.41489505767822}, {11.6989994049072,20.2690544128418,9.41389465332031}, {11.7609996795654,20.4310550689697,9.41489505767822}, {11.8359994888306,19.2130546569824,9.41489505767822}, {14.1889991760254,20.1710548400879,9.41489505767822}, {13.9689998626709,20.2840557098389,9.41489505767822}, {13.8739995956421,20.315055847168,9.41489505767822}, {13.7799997329712,18.8080558776855,9.41489505767822}, {13.9869995117188,20.2750549316406,9.41489505767822}, {12.3129997253418,20.5980548858643,9.41489505767822}, {12.3399991989136,20.5090560913086,9.41489505767822}, {12.3489990234375,20.3830547332764,9.41489505767822}, {12.3599996566772,20.2680549621582,9.41489505767822}, {12.3849992752075,20.1850547790527,9.41489505767822}, {12.3849992752075,20.1670551300049,9.41489505767822}, {12.4249992370605,20.065055847168,9.41489505767822}, {12.4729995727539,19.1350555419922,9.41489505767822}, {14.4399995803833,19.2900543212891,9.41489505767822}, {14.3649997711182,18.5740547180176,9.41489505767822}, {13.5729999542236,20.0310554504395,9.41489505767822}, {13.4889993667603,19.9140548706055,9.41489505767822}, {13.5639991760254,18.8710556030273,9.41489505767822}, {13.6389999389648,20.1310558319092,9.41489505767822}, {13.6719989776611,20.2130546569824,9.41489505767822}, {13.75,20.3020553588867,9.41489505767822}, {12.7399997711182,19.7810554504395,9.41489505767822}, {12.6189994812012,19.8520545959473,9.41489505767822}, {12.5799999237061,19.1200542449951,9.41489505767822}, {12.8349990844727,19.069055557251,9.41489505767822}, {11.2669992446899,19.9350547790527,9.41489505767822}, {11.1029987335205,19.9230556488037,9.41489505767822}, {11.0209999084473,19.2600555419922,9.41489505767822}, {11.3819999694824,19.9710559844971,9.41489505767822}, {13.418999671936,19.8530559539795,9.41489505767822}, {13.4329996109009,18.9160556793213,9.41489505767822}, {11.8399991989136,20.6430549621582,9.41489505767822}, {13.3119993209839,19.7800559997559,9.41489505767822}, {15.2189998626709,19.2600555419922,9.41489505767822}, {15.1839990615845,18.1600551605225,9.41489505767822}, {15.3639993667603,18.0520553588867,9.41489505767822}, {13.0189990997314,19.7250556945801,9.41489505767822}, {12.8949995040894,19.7350559234619,9.41489505767822}, {15.9039993286133,19.1500549316406,9.41489505767822}, {15.7699995040894,19.5140552520752,9.41489505767822}, {15.8589992523193,18.9340553283691,9.41489505767822}, {14.1939992904663,19.9510555267334,9.41489505767822}, {14.2119998931885,20.0630550384521,9.41489505767822}, {14.8589992523193,19.149055480957,9.41489505767822}, {14.8159999847412,18.3670558929443,9.41489505767822}, {14.8959999084473,18.3220558166504,9.41489505767822}, {12.5189990997314,19.9360542297363,9.41489505767822}, {11.0209999084473,19.9290542602539,9.41489505767822}, {11.0209999084473,19.2530555725098,2.67489433288574}, {11.0209999084473,19.9300556182861,2.67489433288574}, {15.9799995422363,18.505931854248,5.58724021911621}, {15.9799995422363,18.5044555664062,9.41489505767822}, {15.9799995422363,18.5041732788086,2.67489433288574}, {15.9799995422363,18.1684837341309,2.67489433288574}, {15.9799995422363,18.1288299560547,9.41489505767822}, {15.9799995422363,17.9876575469971,2.67489433288574}, {15.9799995422363,17.6247596740723,3.91620373725891}, {15.9799995422363,17.6247596740723,2.67489433288574}, {15.9799995422363,17.6254329681396,4.32245063781738}, {15.9799995422363,17.8920269012451,9.41489505767822}, {15.9799995422363,17.8795108795166,2.67489433288574}, {15.9799995422363,17.629810333252,4.58585262298584}, {15.9799995422363,17.6336059570312,5.27938556671143}, {15.9799995422363,17.8311748504639,2.67489433288574}, {15.9799995422363,17.638355255127,9.41489505767822}, {15.9799995422363,17.6346111297607,5.98653984069824}, {15.9799995422363,17.8728256225586,2.67489433288574}, {15.9799995422363,18.2221603393555,2.67489433288574} }, + { {15.8899993896484,19.444055557251,2.67489433288574}, {15.9129991531372,19.1590557098389,2.67489433288574}, {15.9039993286133,19.1500549316406,2.67489433288574}, {15.9489994049072,19.2490558624268,2.67489433288574}, + {15.9579992294312,19.3570556640625,2.67489433288574}, {15.8819999694824,18.690055847168,2.67489433288574}, {15.8319997787476,17.7460556030273,2.67489433288574}, {15.8489999771118,18.819055557251,2.67489433288574}, + {15.8589992523193,17.7190551757812,2.67489433288574}, {15.8769998550415,19.0490550994873,2.67489433288574}, {15.7529993057251,17.8080558776855,2.67489433288574}, {15.7869997024536,19.5010547637939,2.67489433288574}, + {14.0329990386963,18.7170543670654,2.67489433288574}, {13.9599990844727,18.7460556030273,2.67489433288574}, {13.9869995117188,20.2840557098389,2.67489433288574}, {14.2029991149902,20.149055480957,2.67489433288574}, + {14.1939992904663,19.9560546875,2.67489433288574}, {14.1939992904663,20.1670551300049,2.67489433288574}, {14.2119998931885,20.0590553283691,2.67489433288574}, {12.1899995803833,19.1840553283691,2.67489433288574}, + {12.096999168396,19.1950550079346,2.67489433288574}, {12.1099996566772,20.6690559387207,2.67489433288574}, {11.382999420166,19.9750556945801,2.67489433288574}, {11.2599992752075,19.2490558624268,2.67489433288574}, + {11.2369995117188,19.9320545196533,2.67489433288574}, {11.5349998474121,20.0640544891357,2.67489433288574}, {11.6259994506836,20.1550559997559,2.67489433288574}, {11.6829986572266,20.2390556335449,2.67489433288574}, + {11.7369995117188,20.3570556640625,2.67489433288574}, {11.8449993133545,20.645055770874,2.67489433288574}, {11.7729988098145,20.4640560150146,2.67489433288574}, {11.7799987792969,20.5370559692383,9.41389465332031}, + {11.7639999389648,20.4470558166504,2.67489433288574}, {11.9559993743896,20.6810550689697,2.67489433288574}, {12.3079996109009,20.6020545959473,2.67489433288574}, {12.1959991455078,19.1860542297363,2.67489433288574}, + {12.2059993743896,20.6540546417236,2.67489433288574}, {12.3489990234375,20.3740558624268,2.67489433288574}, {12.3579998016357,20.2750549316406,2.67489433288574}, {12.3669996261597,20.266056060791,2.67489433288574}, + {12.3849992752075,20.1670551300049,2.67489433288574}, {12.4269990921021,20.0680541992188,2.67489433288574}, {12.5029993057251,19.9540557861328,2.67489433288574}, {12.6169996261597,19.8550548553467,2.67489433288574}, + {12.7449989318848,19.7800559997559,2.67489433288574}, {12.7629995346069,19.7800559997559,2.67489433288574}, {12.8799991607666,19.7350559234619,2.67489433288574}, {13.0369997024536,19.7250556945801,2.67489433288574}, + {13.0149993896484,19.0340557098389,2.67489433288574}, {11.1699991226196,19.2580547332764,2.67489433288574}, {11.0959987640381,19.2580547332764,2.67489433288574}, {11.1209993362427,19.9230556488037,2.67489433288574}, + {13.0599994659424,19.024055480957,2.67489433288574}, {14.9049997329712,18.3170547485352,2.67489433288574}, {14.8779993057251,18.3400554656982,2.67489433288574}, {14.8779993057251,19.149055480957,2.67489433288574}, + {13.3039989471436,19.77805519104,2.67489433288574}, {13.1589994430542,18.9890556335449,2.67489433288574}, {13.1559991836548,19.7350559234619,2.67489433288574}, {13.4269990921021,19.8600559234619,2.67489433288574}, + {13.5339994430542,19.9700546264648,2.67389440536499}, {13.6359996795654,20.1220550537109,2.67489433288574}, {13.6359996795654,20.1400547027588,2.67489433288574}, {13.6719989776611,20.2210559844971,2.67489433288574}, + {13.6899995803833,20.2300548553467,2.67489433288574}, {13.7509994506836,20.3010559082031,2.67489433288574}, {13.8539991378784,20.3180541992188,2.67489433288574}, {14.8329992294312,18.3580551147461,2.67489433288574}, + {14.1849994659424,19.8530559539795,2.67489433288574}, {14.0769996643066,18.7000541687012,2.67489433288574}, {14.1099996566772,20.2400550842285,2.67489433288574}, {14.2009992599487,19.6230545043945,2.67489433288574}, + {14.2729997634888,19.4670543670654,2.67489433288574}, {14.3379993438721,19.3790550231934,2.67489433288574}, {14.4549999237061,19.2770557403564,2.67489433288574}, {14.5899991989136,19.2040557861328,2.67489433288574}, + {14.6079998016357,19.2040557861328,2.67489433288574}, {14.7209997177124,19.1600551605225,2.67489433288574}, {15.1379995346069,19.210054397583,2.67489433288574}, {14.9949998855591,18.2680549621582,2.67489433288574}, + {15.0029993057251,19.1580543518066,2.67489433288574}, {15.2369995117188,19.2760543823242,2.67489433288574}, {15.3779993057251,19.4060554504395,2.67489433288574}, {15.4539995193481,19.520055770874,2.67489433288574}, + {15.471999168396,19.52805519104,2.67489433288574}, {15.5449991226196,19.5830554962158,2.67489433288574}, {15.6529998779297,19.573055267334,2.67489433288574}, {15.7059993743896,17.8360557556152,2.67489433288574}, + {15.9449996948242,18.5560550689697,2.67489433288574}, {15.8589992523193,18.9380550384521,2.67489433288574}, {14.9589996337891,18.2950553894043,2.67489433288574}, {15.7779998779297,19.5100555419922,2.67489433288574}, + {14.0049991607666,20.2750549316406,2.67489433288574}, {12.3489990234375,20.5000553131104,2.67489433288574}, {13.0689992904663,19.0150547027588,2.67489433288574}, {13.0999994277954,19.0100555419922,2.67489433288574}, + {15.9489994049072,19.3670558929443,9.41489505767822}, {15.9489994049072,19.2490558624268,9.41489505767822}, {15.75,17.8080558776855,9.41489505767822}, {15.6639995574951,19.5710544586182,9.41489505767822}, + {15.5709991455078,17.9260559082031,9.41489505767822}, {15.8769998550415,18.690055847168,9.41489505767822}, {15.8499994277954,18.8170547485352,9.41489505767822}, {15.9459991455078,18.5520553588867,9.41489505767822}, + {15.914999961853,17.6890544891357,9.41489505767822}, {15.3999996185303,19.4290542602539,9.41489505767822}, {15.3099994659424,19.339054107666,9.41489505767822}, {15.3729991912842,18.0440559387207,9.41489505767822}, + {15.4579992294312,19.5170555114746,9.41489505767822}, {15.5469999313354,19.5820541381836,9.41489505767822}, {13.2309989929199,19.7610549926758,9.41489505767822}, {13.168999671936,19.7360553741455,9.41489505767822}, + {13.096999168396,19.0140552520752,9.41489505767822}, {13.1999988555908,18.9870548248291,9.41489505767822}, {15.1399993896484,19.2080554962158,9.41489505767822}, {15.0159997940063,19.1600551605225,9.41489505767822}, + {14.9859991073608,18.2770557403564,9.41489505767822}, {15.1749992370605,18.1690559387207,9.41489505767822}, {15.9039993286133,19.1320552825928,9.41489505767822}, {15.8949995040894,19.4460544586182,9.41489505767822}, + {15.8769998550415,19.0420551300049,9.41489505767822}, {12.2169990539551,20.6500549316406,9.41489505767822}, {11.9379997253418,20.6810550689697,9.41489505767822}, {11.8629989624023,19.2130546569824,9.41489505767822}, {12.096999168396,19.1950550079346,9.41489505767822}, {14.1669998168945,18.6640548706055,9.41489505767822}, {14.1039991378784,20.2460556030273,9.41489505767822}, {13.9849996566772,18.7360553741455,9.41489505767822}, {14.7349996566772,19.1590557098389,9.41489505767822}, {14.5849990844727,19.2050552368164,9.41489505767822}, {14.5719995498657,18.4850559234619,9.41489505767822}, {14.1939992904663,19.6760559082031,9.41489505767822}, {14.1849994659424,19.9330558776855,9.41489505767822}, {14.1759996414185,18.6640548706055,9.41489505767822}, {14.261999130249,19.4890556335449,9.41489505767822}, {14.3539991378784,19.3610553741455,9.41489505767822}, {14.3559989929199,18.5830554962158,9.41489505767822}, {11.6039991378784,20.1250553131104,9.41489505767822}, {11.5209999084473,20.0520553588867,9.41489505767822}, {11.4209995269775,19.2480545043945,9.41489505767822}, {11.6989994049072,20.2690544128418,9.41389465332031}, {11.7609996795654,20.4310550689697,9.41489505767822}, {11.8359994888306,19.2130546569824,9.41489505767822}, {14.1889991760254,20.1710548400879,9.41489505767822}, {13.9689998626709,20.2840557098389,9.41489505767822}, {13.8739995956421,20.315055847168,9.41489505767822}, {13.7799997329712,18.8080558776855,9.41489505767822}, {13.9869995117188,20.2750549316406,9.41489505767822}, {12.3129997253418,20.5980548858643,9.41489505767822}, {12.3399991989136,20.5090560913086,9.41489505767822}, {12.3489990234375,20.3830547332764,9.41489505767822}, {12.3599996566772,20.2680549621582,9.41489505767822}, {12.3849992752075,20.1850547790527,9.41489505767822}, {12.3849992752075,20.1670551300049,9.41489505767822}, {12.4249992370605,20.065055847168,9.41489505767822}, {12.4729995727539,19.1350555419922,9.41489505767822}, {14.4399995803833,19.2900543212891,9.41489505767822}, {14.3649997711182,18.5740547180176,9.41489505767822}, {13.5729999542236,20.0310554504395,9.41489505767822}, {13.4889993667603,19.9140548706055,9.41489505767822}, {13.5639991760254,18.8710556030273,9.41489505767822}, {13.6389999389648,20.1310558319092,9.41489505767822}, {13.6719989776611,20.2130546569824,9.41489505767822}, {13.75,20.3020553588867,9.41489505767822}, {12.7399997711182,19.7810554504395,9.41489505767822}, {12.6189994812012,19.8520545959473,9.41489505767822}, {12.5799999237061,19.1200542449951,9.41489505767822}, {12.8349990844727,19.069055557251,9.41489505767822}, {11.2669992446899,19.9350547790527,9.41489505767822}, {11.1029987335205,19.9230556488037,9.41489505767822}, {11.0209999084473,19.2600555419922,9.41489505767822}, {11.3819999694824,19.9710559844971,9.41489505767822}, {13.418999671936,19.8530559539795,9.41489505767822}, {13.4329996109009,18.9160556793213,9.41489505767822}, {11.8399991989136,20.6430549621582,9.41489505767822}, {13.3119993209839,19.7800559997559,9.41489505767822}, {15.2189998626709,19.2600555419922,9.41489505767822}, {15.1839990615845,18.1600551605225,9.41489505767822}, {15.3639993667603,18.0520553588867,9.41489505767822}, {13.0189990997314,19.7250556945801,9.41489505767822}, {12.8949995040894,19.7350559234619,9.41489505767822}, {15.9039993286133,19.1500549316406,9.41489505767822}, {15.7699995040894,19.5140552520752,9.41489505767822}, {15.8589992523193,18.9340553283691,9.41489505767822}, {14.1939992904663,19.9510555267334,9.41489505767822}, {14.2119998931885,20.0630550384521,9.41489505767822}, {14.8589992523193,19.149055480957,9.41489505767822}, {14.8159999847412,18.3670558929443,9.41489505767822}, {14.8959999084473,18.3220558166504,9.41489505767822}, {12.5189990997314,19.9360542297363,9.41489505767822}, {11.0209999084473,19.9290542602539,9.41489505767822}, {11.0209999084473,19.2530555725098,2.67489433288574}, {11.0209999084473,19.9300556182861,2.67489433288574}, {15.9799995422363,18.505931854248,5.58724021911621}, {15.9799995422363,18.5044555664062,9.41489505767822}, {15.9799995422363,18.5041732788086,2.67489433288574}, {15.9799995422363,18.1684837341309,2.67489433288574}, {15.9799995422363,18.1288299560547,9.41489505767822}, {15.9799995422363,17.9876575469971,2.67489433288574}, {15.9799995422363,17.6247596740723,3.91620373725891}, {15.9799995422363,17.6247596740723,2.67489433288574}, {15.9799995422363,17.6254329681396,4.32245063781738}, {15.9799995422363,17.8920269012451,9.41489505767822}, {15.9799995422363,17.8795108795166,2.67489433288574}, {15.9799995422363,17.629810333252,4.58585262298584}, {15.9799995422363,17.6336059570312,5.27938556671143}, {15.9799995422363,17.8311748504639,2.67489433288574}, {15.9799995422363,17.638355255127,9.41489505767822}, {15.9799995422363,17.6346111297607,5.98653984069824}, {15.9799995422363,17.8728256225586,2.67489433288574}, {15.9799995422363,18.2221603393555,2.67489433288574} }, { {0,1,2}, {0,3,1}, {0,4,3}, {5,6,7}, {8,6,5}, {2,9,0}, {6,10,11}, {12,13,14}, {15,16,17}, {18,16,15}, {19,20,21}, {22,23,24}, {25,23,22}, {26,23,25}, {27,23,26}, {28,23,27}, {29,30,31}, {29,32,30}, {29,28,32}, {33,28,29}, {33,23,28}, {21,23,33}, {20,23,21}, {34,35,36}, {37,35,34}, {38,35,37}, {39,35,38}, {40,35,39}, {41,35,40}, {42,35,41}, {43,35,42}, {44,35,43}, {45,35,44}, {46,35,45}, {47,35,46}, {48,35,47}, {49,50,51}, {52,48,47}, {23,49,24}, {53,54,55}, {56,57,58}, {59,57,56}, {60,57,59}, {61,57,60}, {62,57,61}, {63,57,62}, {64,57,63}, {65,57,64}, {66,57,65}, {13,57,66}, {54,67,55}, {68,69,70}, {71,69,68}, {72,69,71}, {73,69,72}, {74,69,73}, {75,69,74}, {76,69,75}, {77,69,76}, {67,69,77}, {70,16,68}, {70,17,16}, {78,79,80}, {81,79,78}, {82,79,81}, {83,79,82}, {84,79,83}, {85,79,84}, {86,79,85}, {87,79,86}, {88,8,5}, {11,7,6}, {11,89,7}, {11,9,89}, {11,0,9}, {55,90,53}, {55,79,90}, {55,80,79}, {91,11,10}, {92,69,12}, {92,70,69}, {34,93,37}, {47,94,52}, {47,95,94}, {47,57,95}, {47,58,57}, {51,24,49}, {21,35,19}, {21,36,35}, {14,92,12}, {86,10,87}, {86,91,10}, {77,55,67}, {66,14,13}, {96,97,4}, {98,99,100}, {101,102,98}, {103,101,98}, {104,103,98}, {105,106,107}, {108,105,107}, {109,108,107}, {100,109,107}, {110,111,112}, {113,110,112}, {114,115,116}, {117,114,116}, {118,119,120}, {121,122,123}, {124,121,123}, {125,126,127}, {128,129,130}, {131,132,133}, {71,131,133}, {134,71,133}, {135,134,133}, {136,135,133}, {137,138,139}, {140,137,139}, {141,140,139}, {142,31,141}, {142,141,139}, {143,126,132}, {144,145,146}, {147,144,146}, {127,147,146}, {148,121,124}, {149,148,124}, {150,149,124}, {151,150,124}, {152,151,124}, {153,152,124}, {154,153,124}, {155,154,124}, {129,156,157}, {130,129,157}, {158,159,160}, {161,158,160}, {162,161,160}, {163,162,160}, {146,163,160}, {164,165,166}, {167,164,166}, {168,169,170}, {171,168,170}, {139,171,170}, {159,172,173}, {123,174,142}, {175,110,113}, {173,175,113}, {106,176,177}, {178,106,177}, {179,180,167}, {112,179,167}, {175,173,172}, {119,118,181}, {119,181,97}, {119,97,96}, {182,98,102}, {182,102,183}, {182,183,120}, {182,120,119}, {143,132,184}, {184,185,143}, {147,127,126}, {174,123,122}, {159,173,160}, {126,125,133}, {126,133,132}, {186,187,188}, {186,188,116}, {186,116,115}, {99,98,182}, {109,100,99}, {106,178,107}, {114,117,177}, {114,177,176}, {128,130,187}, {128,187,186}, {135,136,157}, {135,157,156}, {163,146,145}, {164,167,180}, {179,112,111}, {171,139,138}, {189,155,166}, {189,166,165}, {149,150,93}, {154,155,189}, {31,142,174}, {114,176,78}, {81,78,176}, {7,89,183}, {89,9,120}, {89,120,183}, {78,80,114}, {176,106,81}, {88,5,103}, {183,102,7}, {118,120,9}, {9,2,181}, {9,181,118}, {115,114,80}, {82,81,106}, {101,103,5}, {102,101,5}, {5,7,102}, {97,181,2}, {2,1,97}, {1,3,97}, {80,55,115}, {172,159,59}, {59,56,172}, {3,4,97}, {4,0,96}, {105,108,82}, {186,115,55}, {82,106,105}, {83,82,108}, {60,59,159}, {175,172,56}, {119,96,0}, {0,11,119}, {108,109,84}, {84,83,108}, {55,77,186}, {56,58,110}, {56,110,175}, {60,159,158}, {11,91,182}, {182,119,11}, {91,86,182}, {85,84,109}, {86,85,99}, {128,186,77}, {58,111,110}, {158,161,60}, {26,25,137}, {138,137,25}, {99,182,86}, {109,99,85}, {77,76,128}, {58,47,111}, {61,60,161}, {137,140,26}, {27,26,140}, {25,22,138}, {129,128,76}, {76,75,129}, {75,74,129}, {74,73,156}, {73,72,135}, {68,16,184}, {68,184,132}, {16,18,185}, {161,162,62}, {62,61,161}, {179,111,47}, {171,138,22}, {156,129,74}, {135,156,73}, {134,135,72}, {72,71,134}, {68,132,131}, {185,184,16}, {18,15,185}, {63,62,162}, {28,27,140}, {22,24,171}, {71,68,131}, {15,17,143}, {15,143,185}, {17,70,143}, {70,92,126}, {162,163,64}, {64,63,162}, {180,179,47}, {47,46,180}, {140,141,28}, {168,171,24}, {126,143,70}, {92,14,147}, {147,126,92}, {14,66,144}, {14,144,147}, {65,64,163}, {66,65,145}, {46,45,180}, {32,28,141}, {24,51,168}, {145,144,66}, {163,145,65}, {164,180,45}, {45,44,164}, {44,43,164}, {43,42,165}, {38,37,151}, {150,151,37}, {37,93,150}, {141,31,30}, {30,32,141}, {169,168,51}, {165,164,43}, {189,165,42}, {42,41,189}, {40,39,152}, {40,152,153}, {151,152,39}, {39,38,151}, {93,34,149}, {154,189,41}, {153,154,41}, {41,40,153}, {148,149,34}, {34,36,148}, {36,21,121}, {31,174,29}, {121,148,36}, {21,33,122}, {21,122,121}, {33,29,122}, {174,122,29}, {116,188,53}, {104,98,10}, {87,10,98}, {98,100,87}, {79,87,100}, {79,100,107}, {90,79,107}, {90,107,178}, {178,177,90}, {53,90,177}, {53,177,117}, {117,116,53}, {54,53,188}, {54,188,187}, {67,54,187}, {67,187,130}, {69,67,130}, {69,130,157}, {12,69,157}, {12,157,136}, {136,133,12}, {12,133,125}, {125,127,12}, {13,12,127}, {127,146,13}, {57,13,146}, {57,146,160}, {95,57,160}, {95,160,173}, {173,113,95}, {94,95,113}, {113,112,94}, {52,94,112}, {48,52,112}, {112,167,48}, {35,48,167}, {35,167,166}, {19,35,166}, {139,170,50}, {50,49,139}, {166,155,19}, {20,19,155}, {155,124,20}, {23,20,124}, {23,124,123}, {49,23,123}, {49,123,142}, {142,139,49}, {190,191,170}, {192,191,190}, {191,192,51}, {191,51,50}, {170,169,190}, {169,51,192}, {169,192,190}, {170,191,50}, {193,194,195}, {196,197,198}, {199,200,201}, {198,202,203}, {204,201,200}, {205,204,200}, {206,207,208}, {206,208,205}, {206,205,200}, {207,206,209}, {207,209,203}, {207,203,202}, {202,198,197}, {197,196,210}, {197,210,195}, {197,195,194}, {8,88,195}, {8,195,210}, {210,196,8}, {196,198,8}, {198,203,8}, {203,209,8}, {209,206,8}, {206,200,8}, {202,197,104}, {207,202,104}, {103,104,197}, {103,197,194}, {193,195,88}, {88,103,194}, {88,194,193}, {200,199,8}, {199,201,8}, {204,205,6}, {6,8,201}, {6,201,204}, {10,6,205}, {10,205,208}, {104,10,208}, {104,208,207} }); break; case TestMesh::pyramid: @@ -108,22 +139,33 @@ TriangleMesh mesh(TestMesh m) break; case TestMesh::two_hollow_squares: mesh = TriangleMesh( - { {66.7133483886719,104.286666870117,0}, {66.7133483886719,95.7133331298828,0}, {65.6666870117188,94.6666717529297,0}, {75.2866821289062,95.7133331298828,0}, {76.3333435058594,105.333335876465,0}, {76.3333435058594,94.6666717529297,0}, {65.6666870117188,105.33332824707,0}, {75.2866821289062,104.286666870117,0}, {71.1066818237305,104.58666229248,2.79999995231628}, {66.4133529663086,104.58666229248,2.79999995231628}, {75.5866851806641,104.58666229248,2.79999995231628}, {66.4133529663086,99.8933334350586,2.79999995231628}, {66.4133529663086,95.4133377075195,2.79999995231628}, {71.1066818237305,95.4133377075195,2.79999995231628}, {75.5866851806641,95.4133377075195,2.79999995231628}, {75.5866851806641,100.106666564941,2.79999995231628}, {74.5400161743164,103.540000915527,2.79999995231628}, {70.0320129394531,103.540000915527,2.79999995231628}, {67.4600067138672,103.540000915527,2.79999995231628}, {67.4600067138672,100.968002319336,2.79999995231628}, {67.4600067138672,96.4599990844727,2.79999995231628}, {74.5400161743164,99.0319976806641,2.79999995231628}, {74.5400161743164,96.4599990844727,2.79999995231628}, {70.0320129394531,96.4599990844727,2.79999995231628}, {123.666717529297,94.6666717529297,0}, {134.333312988281,94.6666717529297,0}, {124.413360595703,95.4133377075195,2.79999995231628}, {129.106674194336,95.4133377075195,2.79999995231628}, {133.586669921875,95.4133377075195,2.79999995231628}, {123.666717529297,105.33332824707,0}, {124.413360595703,104.58666229248,2.79999995231628}, {124.413360595703,99.8933334350586,2.79999995231628}, {134.333312988281,105.33332824707,0}, {129.106674194336,104.58666229248,2.79999995231628}, {133.586669921875,104.58666229248,2.79999995231628}, {133.586669921875,100.106666564941,2.79999995231628}, {124.713317871094,104.286666870117,0}, {124.713317871094,95.7133331298828,0}, {133.286712646484,95.7133331298828,0}, {133.286712646484,104.286666870117,0}, {132.540023803711,103.540000915527,2.79999995231628}, {128.032028198242,103.540008544922,2.79999995231628}, {125.460006713867,103.540000915527,2.79999995231628}, {125.460006713867,100.968002319336,2.79999995231628}, {125.460006713867,96.4599990844727,2.79999995231628}, {132.540023803711,99.0319976806641,2.79999995231628}, {132.540023803711,96.4599990844727,2.79999995231628}, {128.032028198242,96.4599990844727,2.79999995231628} }, + { {66.7133483886719f,104.286666870117f,0}, {66.7133483886719f,95.7133331298828f,0}, {65.6666870117188f,94.6666717529297f,0}, {75.2866821289062f,95.7133331298828f,0}, {76.3333435058594f,105.333335876465f,0}, + {76.3333435058594f,94.6666717529297f,0}, {65.6666870117188f,105.33332824707f,0}, {75.2866821289062f,104.286666870117f,0}, {71.1066818237305f,104.58666229248f,2.79999995231628f}, {66.4133529663086f,104.58666229248f,2.79999995231628f}, + {75.5866851806641f,104.58666229248f,2.79999995231628f}, {66.4133529663086f,99.8933334350586f,2.79999995231628f}, {66.4133529663086f,95.4133377075195f,2.79999995231628f}, {71.1066818237305f,95.4133377075195f,2.79999995231628f}, + {75.5866851806641f,95.4133377075195f,2.79999995231628f}, {75.5866851806641f,100.106666564941f,2.79999995231628f}, {74.5400161743164f,103.540000915527f,2.79999995231628f}, {70.0320129394531f,103.540000915527f,2.79999995231628f}, + {67.4600067138672f,103.540000915527f,2.79999995231628f}, {67.4600067138672f,100.968002319336f,2.79999995231628f}, {67.4600067138672f,96.4599990844727f,2.79999995231628f}, {74.5400161743164f,99.0319976806641f,2.79999995231628f}, + {74.5400161743164f,96.4599990844727f,2.79999995231628f}, {70.0320129394531f,96.4599990844727f,2.79999995231628f}, {123.666717529297f,94.6666717529297f,0}, {134.333312988281f,94.6666717529297f,0}, {124.413360595703f,95.4133377075195f,2.79999995231628f}, + {129.106674194336f,95.4133377075195f,2.79999995231628f}, {133.586669921875f,95.4133377075195f,2.79999995231628f}, {123.666717529297f,105.33332824707f,0}, {124.413360595703f,104.58666229248f,2.79999995231628f}, + {124.413360595703f,99.8933334350586f,2.79999995231628f}, {134.333312988281f,105.33332824707f,0}, {129.106674194336f,104.58666229248f,2.79999995231628f}, {133.586669921875f,104.58666229248f,2.79999995231628f}, + {133.586669921875f,100.106666564941f,2.79999995231628f}, {124.713317871094f,104.286666870117f,0}, {124.713317871094f,95.7133331298828f,0}, {133.286712646484f,95.7133331298828f,0}, {133.286712646484f,104.286666870117f,0}, + {132.540023803711f,103.540000915527f,2.79999995231628f}, {128.032028198242f,103.540008544922f,2.79999995231628f}, {125.460006713867f,103.540000915527f,2.79999995231628f}, {125.460006713867f,100.968002319336f,2.79999995231628f}, + {125.460006713867f,96.4599990844727f,2.79999995231628f}, {132.540023803711f,99.0319976806641f,2.79999995231628f}, {132.540023803711f,96.4599990844727f,2.79999995231628f}, {128.032028198242f,96.4599990844727f,2.79999995231628f} }, { {0,1,2}, {3,4,5}, {6,4,0}, {6,0,2}, {2,1,5}, {7,4,3}, {1,3,5}, {0,4,7}, {4,6,8}, {6,9,8}, {4,8,10}, {6,2,9}, {2,11,9}, {2,12,11}, {2,5,12}, {5,13,12}, {5,14,13}, {4,10,15}, {5,4,14}, {4,15,14}, {7,16,17}, {0,7,18}, {7,17,18}, {1,19,20}, {1,0,19}, {0,18,19}, {7,3,21}, {3,22,21}, {7,21,16}, {3,23,22}, {3,1,23}, {1,20,23}, {24,25,26}, {25,27,26}, {25,28,27}, {29,24,30}, {24,31,30}, {24,26,31}, {32,29,33}, {29,30,33}, {32,33,34}, {32,34,35}, {25,32,28}, {32,35,28}, {36,37,24}, {38,32,25}, {29,32,36}, {29,36,24}, {24,37,25}, {39,32,38}, {37,38,25}, {36,32,39}, {39,40,41}, {36,39,42}, {39,41,42}, {37,43,44}, {37,36,43}, {36,42,43}, {39,38,45}, {38,46,45}, {39,45,40}, {38,47,46}, {38,37,47}, {37,44,47}, {16,8,9}, {16,10,8}, {10,16,15}, {15,16,21}, {22,15,21}, {15,22,14}, {22,23,14}, {23,20,14}, {17,16,9}, {18,17,9}, {19,18,9}, {19,9,11}, {19,11,20}, {13,14,20}, {20,11,12}, {13,20,12}, {41,40,30}, {42,41,30}, {43,42,30}, {43,30,31}, {43,31,44}, {27,28,44}, {44,31,26}, {27,44,26}, {40,33,30}, {40,34,33}, {34,40,35}, {35,40,45}, {46,35,45}, {35,46,28}, {46,47,28}, {47,44,28} }); break; case TestMesh::small_dorito: mesh = TriangleMesh( - { {6.00058937072754,-22.9982089996338,0}, {22.0010242462158,-49.9998741149902,0}, {-9.99957847595215,-49.999870300293,0}, {6.00071382522583,-32.2371635437012,28.0019245147705}, {11.1670551300049,-37.9727020263672,18.9601669311523}, {6.00060224533081,-26.5392456054688,10.7321853637695} }, + { {6.00058937072754f,-22.9982089996338f,0}, {22.0010242462158f,-49.9998741149902f,0}, {-9.99957847595215f,-49.999870300293f,0}, {6.00071382522583f,-32.2371635437012f,28.0019245147705f}, + {11.1670551300049f,-37.9727020263672f,18.9601669311523f}, {6.00060224533081f,-26.5392456054688f,10.7321853637695f} }, { {0,1,2}, {3,4,5}, {2,1,4}, {2,4,3}, {2,3,5}, {2,5,0}, {5,4,1}, {5,1,0} }); break; case TestMesh::bridge: mesh = TriangleMesh( - { {75,84.5,8}, {125,84.5,8}, {75,94.5,8}, {120,84.5,5}, {125,94.5,8}, {75,84.5,0}, {80,84.5,5}, {125,84.5,0}, {125,94.5,0}, {80,94.5,5}, {75,94.5,0}, {120,94.5,5}, {120,84.5,0}, {80,94.5,0}, {80,84.5,0}, {120,94.5,0} }, + { {75,84.5f,8}, {125,84.5f,8}, {75,94.5f,8}, {120,84.5f,5}, {125,94.5f,8}, {75,84.5f,0}, {80,84.5f,5}, {125,84.5f,0}, {125,94.5f,0}, {80,94.5f,5}, {75,94.5f,0}, {120,94.5f,5}, {120,84.5f,0}, {80,94.5f,0}, {80,84.5f,0}, {120,94.5f,0} }, { {0,1,2}, {1,0,3}, {2,1,4}, {2,5,0}, {0,6,3}, {1,3,7}, {1,8,4}, {4,9,2}, {10,5,2}, {5,6,0}, {6,11,3}, {3,12,7}, {7,8,1}, {4,8,11}, {4,11,9}, {9,10,2}, {10,13,5}, {14,6,5}, {9,11,6}, {11,12,3}, {12,8,7}, {11,8,15}, {13,10,9}, {5,13,14}, {14,13,6}, {6,13,9}, {15,12,11}, {15,8,12} }); break; case TestMesh::bridge_with_hole: mesh = TriangleMesh( - { {75,69.5,8}, {80,76.9091644287109,8}, {75,94.5,8}, {125,69.5,8}, {120,76.9091644287109,8}, {120,87.0908355712891,8}, {80,87.0908355712891,8}, {125,94.5,8}, {80,87.0908355712891,5}, {120,87.0908355712891,5}, {125,94.5,0}, {120,69.5,0}, {120,94.5,0}, {125,69.5,0}, {120,94.5,5}, {80,94.5,5}, {80,94.5,0}, {75,94.5,0}, {80,69.5,5}, {80,69.5,0}, {80,76.9091644287109,5}, {120,69.5,5}, {75,69.5,0}, {120,76.9091644287109,5} }, + { {75,69.5f,8}, {80,76.9091644287109f,8}, {75,94.5f,8}, {125,69.5f,8}, {120,76.9091644287109f,8}, {120,87.0908355712891f,8}, {80,87.0908355712891f,8}, {125,94.5f,8}, {80,87.0908355712891f,5}, {120,87.0908355712891f,5}, {125,94.5f,0}, {120,69.5f,0}, {120,94.5f,0}, {125,69.5f,0}, {120,94.5f,5}, {80,94.5f,5}, {80,94.5f,0}, {75,94.5f,0}, {80,69.5f,5}, {80,69.5f,0}, {80,76.9091644287109f,5}, {120,69.5,5}, {75,69.5f,0}, {120,76.9091644287109f,5} }, { {0,1,2}, {1,0,3}, {1,3,4}, {4,3,5}, {2,6,7}, {6,2,1}, {7,6,5}, {7,5,3}, {5,8,9}, {8,5,6}, {10,11,12}, {11,10,13}, {14,8,15}, {8,14,9}, {2,16,17}, {16,2,15}, {15,2,14}, {14,10,12}, {10,14,7}, {7,14,2}, {16,18,19}, {18,16,20}, {20,16,1}, {1,16,8}, {8,16,15}, {6,1,8}, {3,11,13}, {11,3,21}, {21,3,18}, {18,22,19}, {22,18,0}, {0,18,3}, {16,22,17}, {22,16,19}, {2,22,0}, {22,2,17}, {5,23,4}, {23,11,21}, {11,23,12}, {12,23,9}, {9,23,5}, {12,9,14}, {23,18,20}, {18,23,21}, {10,3,13}, {3,10,7}, {1,23,20}, {23,1,4} }); break; case TestMesh::step: @@ -133,7 +175,7 @@ TriangleMesh mesh(TestMesh m) break; case TestMesh::slopy_cube: mesh = TriangleMesh( - { {-10,-10,0}, {-10,-10,20}, {-10,10,0}, {-10,10,20}, {0,-10,10}, {10,-10,0}, {2.92893,-10,10}, {10,-10,2.92893}, {0,-10,20}, {10,10,0}, {0,10,10}, {0,10,20}, {2.92893,10,10}, {10,10,2.92893} }, + { {-10,-10,0}, {-10,-10,20}, {-10,10,0}, {-10,10,20}, {0,-10,10}, {10,-10,0}, {2.92893f,-10,10}, {10,-10,2.92893f}, {0,-10,20}, {10,10,0}, {0,10,10}, {0,10,20}, {2.92893f,10,10}, {10,10,2.92893f} }, { {0,1,2}, {2,1,3}, {4,0,5}, {4,1,0}, {6,4,7}, {7,4,5}, {4,8,1}, {0,2,5}, {5,2,9}, {2,10,9}, {10,3,11}, {2,3,10}, {9,10,12}, {13,9,12}, {3,1,8}, {11,3,8}, {10,11,8}, {4,10,8}, {6,12,10}, {4,6,10}, {7,13,12}, {6,7,12}, {7,5,9}, {13,7,9} }); break; default: @@ -141,7 +183,6 @@ TriangleMesh mesh(TestMesh m) break; } - mesh.repair(); return mesh; } diff --git a/tests/fff_print/test_trianglemesh.cpp b/tests/fff_print/test_trianglemesh.cpp index df237db961..6faaf1584c 100644 --- a/tests/fff_print/test_trianglemesh.cpp +++ b/tests/fff_print/test_trianglemesh.cpp @@ -17,12 +17,13 @@ using namespace Slic3r; using namespace std; +static inline TriangleMesh make_cube() { return make_cube(20., 20, 20); } + SCENARIO( "TriangleMesh: Basic mesh statistics") { GIVEN( "A 20mm cube, built from constexpr std::array" ) { - std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; + std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - TriangleMesh cube(vertices, facets); - cube.repair(); + TriangleMesh cube(vertices, facets); THEN( "Volume is appropriate for 20mm square cube.") { REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2); @@ -68,64 +69,11 @@ SCENARIO( "TriangleMesh: Basic mesh statistics") { } } - GIVEN( "A 20mm cube with one corner on the origin") { - const std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; - const std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - - TriangleMesh cube(vertices, facets); - cube.repair(); - - THEN( "Volume is appropriate for 20mm square cube.") { - REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2); - } - - THEN( "Vertices array matches input.") { - for (size_t i = 0U; i < cube.its.vertices.size(); i++) { - REQUIRE(cube.its.vertices.at(i) == vertices.at(i).cast()); - } - for (size_t i = 0U; i < vertices.size(); i++) { - REQUIRE(vertices.at(i).cast() == cube.its.vertices.at(i)); - } - } - THEN( "Vertex count matches vertex array size.") { - REQUIRE(cube.facets_count() == facets.size()); - } - - THEN( "Facet array matches input.") { - for (size_t i = 0U; i < cube.its.indices.size(); i++) { - REQUIRE(cube.its.indices.at(i) == facets.at(i)); - } - - for (size_t i = 0U; i < facets.size(); i++) { - REQUIRE(facets.at(i) == cube.its.indices.at(i)); - } - } - THEN( "Facet count matches facet array size.") { - REQUIRE(cube.facets_count() == facets.size()); - } - -#if 0 - THEN( "Number of normals is equal to the number of facets.") { - REQUIRE(cube.normals().size() == facets.size()); - } -#endif - - THEN( "center() returns the center of the object.") { - REQUIRE(cube.center() == Vec3d(10.0,10.0,10.0)); - } - - THEN( "Size of cube is (20,20,20)") { - REQUIRE(cube.size() == Vec3d(20,20,20)); - } - } } SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") { GIVEN( "A 20mm cube with one corner on the origin") { - const std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; - const std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - TriangleMesh cube(vertices, facets); - cube.repair(); + auto cube = make_cube(); WHEN( "The cube is scaled 200% uniformly") { cube.scale(2.0); @@ -134,7 +82,7 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") { } } WHEN( "The resulting cube is scaled 200% in the X direction") { - cube.scale(Vec3d(2.0, 1, 1)); + cube.scale(Vec3f(2.0, 1, 1)); THEN( "The volume is doubled.") { REQUIRE(abs(cube.volume() - 2*20.0*20.0*20.0) < 1e-2); } @@ -144,7 +92,7 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") { } WHEN( "The cube is scaled 25% in the X direction") { - cube.scale(Vec3d(0.25, 1, 1)); + cube.scale(Vec3f(0.25, 1, 1)); THEN( "The volume is 25% of the previous volume.") { REQUIRE(abs(cube.volume() - 0.25*20.0*20.0*20.0) < 1e-2); } @@ -177,7 +125,10 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") { cube.translate(5.0, 10.0, 0.0); cube.align_to_origin(); THEN( "The third vertex is located at 0,0,0") { - REQUIRE(cube.its.vertices.at(2) == Vec3f(0.0, 0.0, 0.0)); + REQUIRE(cube.its.vertices.at(2) == Vec3f::Zero()); + } + THEN( "Size is OK") { + REQUIRE(cube.stats().size == Vec3f(20.f, 20.f, 20.f)); } } } @@ -185,11 +136,8 @@ SCENARIO( "TriangleMesh: Transformation functions affect mesh as expected.") { SCENARIO( "TriangleMesh: slice behavior.") { GIVEN( "A 20mm cube with one corner on the origin") { - const std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; - const std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - TriangleMesh cube(vertices, facets); - cube.repair(); - + auto cube = make_cube(); + WHEN("Cube is sliced with z = [0+EPSILON,2,4,8,6,8,10,12,14,16,18,20]") { std::vector z { 0+EPSILON,2,4,8,6,8,10,12,14,16,18,20 }; std::vector result = cube.slice(z); @@ -206,12 +154,12 @@ SCENARIO( "TriangleMesh: slice behavior.") { } } GIVEN( "A STL with an irregular shape.") { - const std::vector vertices {{0,0,0},{0,0,20},{0,5,0},{0,5,20},{50,0,0},{50,0,20},{15,5,0},{35,5,0},{15,20,0},{50,5,0},{35,20,0},{15,5,10},{50,5,20},{35,5,10},{35,20,10},{15,20,10}}; + const std::vector vertices {{0,0,0},{0,0,20},{0,5,0},{0,5,20},{50,0,0},{50,0,20},{15,5,0},{35,5,0},{15,20,0},{50,5,0},{35,20,0},{15,5,10},{50,5,20},{35,5,10},{35,20,10},{15,20,10}}; const std::vector facets {{0,1,2},{2,1,3},{1,0,4},{5,1,4},{0,2,4},{4,2,6},{7,6,8},{4,6,7},{9,4,7},{7,8,10},{2,3,6},{11,3,12},{7,12,9},{13,12,7},{6,3,11},{11,12,13},{3,1,5},{12,3,5},{5,4,9},{12,5,9},{13,7,10},{14,13,10},{8,15,10},{10,15,14},{6,11,8},{8,11,15},{15,11,13},{14,15,13}}; - TriangleMesh cube(vertices, facets); - cube.repair(); + auto cube = make_cube(); WHEN(" a top tangent plane is sliced") { + // At Z = 10 we have a top horizontal surface. std::vector slices = cube.slice({5.0, 10.0}); THEN( "its area is included") { REQUIRE(slices.at(0).at(0).area() > 0); @@ -240,9 +188,6 @@ SCENARIO( "make_xxx functions produce meshes.") { THEN("The mesh volume is 20*20*20") { REQUIRE(abs(cube.volume() - 20.0*20.0*20.0) < 1e-2); } - THEN("The resulting mesh is in the repaired state.") { - REQUIRE(cube.repaired == true); - } THEN("There are 12 facets.") { REQUIRE(cube.its.indices.size() == 12); } @@ -266,9 +211,6 @@ SCENARIO( "make_xxx functions produce meshes.") { THEN("Resulting mesh has 2*PI/angle * 4 facets") { REQUIRE(cyl.its.indices.size() == (2*PI/angle)*4); } - THEN("The resulting mesh is in the repaired state.") { - REQUIRE(cyl.repaired == true); - } THEN( "The mesh volume is approximately 10pi * 10^2") { REQUIRE(abs(cyl.volume() - (10.0 * M_PI * std::pow(10,2))) < 1); } @@ -283,9 +225,6 @@ SCENARIO( "make_xxx functions produce meshes.") { REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, 10.f)); } ) == 1); REQUIRE(std::count_if(verts.begin(), verts.end(), [](const Vec3f& t) { return is_approx(t, Vec3f(0.f, 0.f, -10.f)); } ) == 1); } - THEN("The resulting mesh is in the repaired state.") { - REQUIRE(sph.repaired == true); - } THEN( "The mesh volume is approximately 4/3 * pi * 10^3") { REQUIRE(abs(sph.volume() - (4.0/3.0 * M_PI * std::pow(10,3))) < 1); // 1% tolerance? } @@ -295,32 +234,25 @@ SCENARIO( "make_xxx functions produce meshes.") { SCENARIO( "TriangleMesh: split functionality.") { GIVEN( "A 20mm cube with one corner on the origin") { - const std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; - const std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - - TriangleMesh cube(vertices, facets); - cube.repair(); + auto cube = make_cube(); WHEN( "The mesh is split into its component parts.") { - std::vector meshes = cube.split(); + std::vector meshes = cube.split(); THEN(" The bounding box statistics are propagated to the split copies") { REQUIRE(meshes.size() == 1); - REQUIRE((meshes.at(0)->bounding_box() == cube.bounding_box())); + REQUIRE((meshes.front().bounding_box() == cube.bounding_box())); } } } GIVEN( "Two 20mm cubes, each with one corner on the origin, merged into a single TriangleMesh") { - const std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; - const std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - - TriangleMesh cube(vertices, facets); - cube.repair(); - TriangleMesh cube2(vertices, facets); - cube2.repair(); + auto cube = make_cube(); + TriangleMesh cube2(cube); cube.merge(cube2); - cube.repair(); WHEN( "The combined mesh is split") { - std::vector meshes = cube.split(); + THEN( "Number of faces is 2x the source.") { + REQUIRE(cube.facets_count() == 2 * cube2.facets_count()); + } + std::vector meshes = cube.split(); THEN( "Two meshes are in the output vector.") { REQUIRE(meshes.size() == 2); } @@ -330,17 +262,11 @@ SCENARIO( "TriangleMesh: split functionality.") { SCENARIO( "TriangleMesh: Mesh merge functions") { GIVEN( "Two 20mm cubes, each with one corner on the origin") { - const std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; - const std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - - TriangleMesh cube(vertices, facets); - cube.repair(); - TriangleMesh cube2(vertices, facets); - cube2.repair(); + auto cube = make_cube(); + TriangleMesh cube2(cube); WHEN( "The two meshes are merged") { cube.merge(cube2); - cube.repair(); THEN( "There are twice as many facets in the merged mesh as the original.") { REQUIRE(cube.facets_count() == 2 * cube2.facets_count()); } @@ -350,11 +276,7 @@ SCENARIO( "TriangleMesh: Mesh merge functions") { SCENARIO( "TriangleMeshSlicer: Cut behavior.") { GIVEN( "A 20mm cube with one corner on the origin") { - const std::vector vertices { {20,20,0}, {20,0,0}, {0,0,0}, {0,20,0}, {20,20,20}, {0,20,20}, {0,0,20}, {20,0,20} }; - const std::vector facets { {0,1,2}, {0,2,3}, {4,5,6}, {4,6,7}, {0,4,7}, {0,7,1}, {1,7,6}, {1,6,2}, {2,6,5}, {2,5,3}, {4,0,3}, {4,3,5} }; - - TriangleMesh cube(vertices, facets); - cube.repair(); + auto cube = make_cube(); WHEN( "Object is cut at the bottom") { indexed_triangle_set upper {}; indexed_triangle_set lower {}; @@ -384,7 +306,6 @@ TEST_CASE("Regression test for issue #4486 - files take forever to slice") { TriangleMesh mesh; DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/100_000.stl"); - mesh.repair(); config.set("layer_height", 500); config.set("first_layer_height", 250); @@ -412,7 +333,6 @@ TEST_CASE("Profile test for issue #4486 - files take forever to slice") { TriangleMesh mesh; DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); mesh.ReadSTLFile(std::string(testfile_dir) + "test_trianglemesh/4486/10_000.stl"); - mesh.repair(); config.set("layer_height", 500); config.set("first_layer_height", 250); diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index 5ab000d04f..0ebe47a07c 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -65,10 +65,7 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") { // compare meshes TriangleMesh src_mesh = src_model.mesh(); - src_mesh.repair(); - TriangleMesh dst_mesh = dst_model.mesh(); - dst_mesh.repair(); bool res = src_mesh.its.vertices.size() == dst_mesh.its.vertices.size(); if (res) { diff --git a/tests/libslic3r/test_aabbindirect.cpp b/tests/libslic3r/test_aabbindirect.cpp index c0792a9433..3b834c442e 100644 --- a/tests/libslic3r/test_aabbindirect.cpp +++ b/tests/libslic3r/test_aabbindirect.cpp @@ -9,7 +9,6 @@ using namespace Slic3r; TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]") { TriangleMesh tmesh = make_cube(1., 1., 1.); - tmesh.repair(); auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(tmesh.its.vertices, tmesh.its.indices); REQUIRE(! tree.empty()); diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 1f5ca38458..ad4f8f2961 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -13,7 +13,6 @@ TEST_CASE("Hollow two overlapping spheres") { sphere2.translate( 5.f, 0.f, 0.f); sphere1.merge(sphere2); - sphere1.require_shared_vertices(); sla::hollow_mesh(sphere1, sla::HollowingConfig{}, sla::HollowingFlags::hfRemoveInsideTriangles); diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index a0b27f6d87..3553697acd 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -319,7 +319,6 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { mesh.translate(tr.x(), tr.y(), tr.z()); bb = mesh.bounding_box(); - assert(mesh.has_shared_vertices()); std::vector layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh)); sla::RasterBase::Resolution res{2560, 1440}; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index d26d6e2a23..db8c5e93ec 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -57,7 +57,6 @@ TEST_CASE("Support point generator should be deterministic if seeded", auto layer_h = 0.05f; auto slicegrid = grid(float(gnd), float(zmax), layer_h); - assert(mesh.has_shared_vertices()); std::vector slices = slice_mesh_ex(mesh.its, slicegrid, CLOSING_RADIUS); point_gen.seed(0); diff --git a/tests/sla_print/sla_raycast_tests.cpp b/tests/sla_print/sla_raycast_tests.cpp index b56909280b..a69e094129 100644 --- a/tests/sla_print/sla_raycast_tests.cpp +++ b/tests/sla_print/sla_raycast_tests.cpp @@ -63,7 +63,6 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]") sla::DrainHoles holes = { sla::DrainHole{p, normal, radius, hole_length} }; cube.merge(*cube_inside); - cube.require_shared_vertices(); sla::IndexedMesh emesh{cube}; emesh.load_holes(holes); diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index e160504de8..f2cf7e8348 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -13,7 +13,6 @@ TEST_CASE("Overhanging point should be supported", "[SupGen]") { // Pyramid with 45 deg slope TriangleMesh mesh = make_pyramid(10.f, 10.f); mesh.rotate_y(float(PI)); - mesh.require_shared_vertices(); mesh.WriteOBJFile("Pyramid.obj"); sla::SupportPoints pts = calc_support_pts(mesh); @@ -56,7 +55,6 @@ TEST_CASE("Overhanging horizontal surface should be supported", "[SupGen]") { TriangleMesh mesh = make_cube(width, depth, height); mesh.translate(0., 0., 5.); // lift up - mesh.require_shared_vertices(); mesh.WriteOBJFile("Cuboid.obj"); sla::SupportPointGenerator::Config cfg; @@ -83,7 +81,6 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") { TriangleMesh mesh = make_prism(width, depth, height); mesh.rotate_y(float(PI)); // rotate on its back mesh.translate(0., 0., height); - mesh.require_shared_vertices(); mesh.WriteOBJFile("Prism.obj"); sla::SupportPointGenerator::Config cfg; @@ -115,7 +112,6 @@ TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowe auto h = float(bb.max.z() - bb.min.z()); Vec3f mv = bb.center().cast() - Vec3f{0.f, 0.f, 0.5f * h}; mesh.translate(-mv); - mesh.require_shared_vertices(); sla::SupportPointGenerator::Config cfg; sla::SupportPoints pts = calc_support_pts(mesh, cfg); @@ -132,7 +128,6 @@ TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]") TriangleMesh mesh_high = center_around_bb(make_cube(width, depth, height)); mesh_high.translate(0., 0., 10.); // lift up mesh.merge(mesh_high); - mesh.require_shared_vertices(); mesh.WriteOBJFile("parallel_plates.obj"); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index be9bf9741d..1082df2007 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -74,8 +74,6 @@ void export_failed_case(const std::vector &support_slices, const Sup byproducts.supporttree.retrieve_full_mesh(its); TriangleMesh m{its}; m.merge(byproducts.input_mesh); - m.repair(); - m.require_shared_vertices(); m.WriteOBJFile((Catch::getResultCapture().getCurrentTestName() + "_" + byproducts.obj_fname).c_str()); } @@ -95,7 +93,6 @@ void test_supports(const std::string &obj_filename, sla::InteriorPtr interior = sla::generate_interior(mesh, hollowingcfg); REQUIRE(interior); mesh.merge(TriangleMesh{sla::get_mesh(*interior)}); - mesh.require_shared_vertices(); } auto bb = mesh.bounding_box(); @@ -105,7 +102,6 @@ void test_supports(const std::string &obj_filename, auto layer_h = 0.05f; out.slicegrid = grid(float(gnd), float(zmax), layer_h); - assert(mesh.has_shared_vertices()); out.model_slices = slice_mesh_ex(mesh.its, out.slicegrid, CLOSING_RADIUS); sla::cut_drainholes(out.model_slices, out.slicegrid, CLOSING_RADIUS, drainholes, []{}); @@ -283,8 +279,10 @@ void test_concave_hull(const ExPolygons &polys) { _test_concave_hull(waffl, polys); } +//FIXME this functionality is gone after TriangleMesh refactoring to get rid of admesh. void check_validity(const TriangleMesh &input_mesh, int flags) { + /* TriangleMesh mesh{input_mesh}; if (flags & ASSUME_NO_EMPTY) { @@ -292,20 +290,18 @@ void check_validity(const TriangleMesh &input_mesh, int flags) } else if (mesh.empty()) return; // If it can be empty and it is, there is nothing left to do. - REQUIRE(stl_validate(&mesh.stl)); - bool do_update_shared_vertices = false; mesh.repair(do_update_shared_vertices); if (flags & ASSUME_NO_REPAIR) { - REQUIRE_FALSE(mesh.needed_repair()); + REQUIRE_FALSE(mesh.repaired()); } if (flags & ASSUME_MANIFOLD) { - mesh.require_shared_vertices(); if (!mesh.is_manifold()) mesh.WriteOBJFile("non_manifold.obj"); REQUIRE(mesh.is_manifold()); } + */ } void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::TMirroring mirroring) @@ -420,53 +416,6 @@ double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) return error; } - -// Make a 3D pyramid -TriangleMesh make_pyramid(float base, float height) -{ - float a = base / 2.f; - - TriangleMesh mesh( - { - {-a, -a, 0}, {a, -a, 0}, {a, a, 0}, - {-a, a, 0}, {0.f, 0.f, height} - }, - { - {0, 1, 2}, - {0, 2, 3}, - {0, 1, 4}, - {1, 2, 4}, - {2, 3, 4}, - {3, 0, 4} - }); - - mesh.repair(); - - return mesh; -} - - TriangleMesh make_prism(double width, double length, double height) -{ - // We need two upward facing triangles - - double x = width / 2., y = length / 2.; - - TriangleMesh mesh( - { - {-x, -y, 0.}, {x, -y, 0.}, {0., -y, height}, - {-x, y, 0.}, {x, y, 0.}, {0., y, height}, - }, - { - {0, 1, 2}, // side 1 - {4, 3, 5}, // side 2 - {1, 4, 2}, {2, 4, 5}, // roof 1 - {0, 2, 5}, {0, 5, 3}, // roof 2 - {3, 4, 1}, {3, 1, 0} // bottom - }); - - return mesh; -} - sla::SupportPoints calc_support_pts( const TriangleMesh & mesh, const sla::SupportPointGenerator::Config &cfg) @@ -474,7 +423,6 @@ sla::SupportPoints calc_support_pts( // Prepare the slice grid and the slices auto bb = cast(mesh.bounding_box()); std::vector heights = grid(bb.min.z(), bb.max.z(), 0.1f); - assert(mesh.has_shared_vertices()); std::vector slices = slice_mesh_ex(mesh.its, heights, CLOSING_RADIUS); // Prepare the support point calculator diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index d10a85b259..2264ad856c 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -185,11 +185,6 @@ long raster_pxsum(const sla::RasterGrayscaleAA &raster); double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); -// Make a 3D pyramid -TriangleMesh make_pyramid(float base, float height); - -TriangleMesh make_prism(double width, double length, double height); - sla::SupportPoints calc_support_pts( const TriangleMesh & mesh, const sla::SupportPointGenerator::Config &cfg = {}); diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t index 4013a1f830..453cc92189 100644 --- a/xs/t/01_trianglemesh.t +++ b/xs/t/01_trianglemesh.t @@ -4,10 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 49; - -is Slic3r::TriangleMesh::hello_world(), 'Hello world!', - 'hello world'; +use Test::More tests => 5; my $cube = { vertices => [ [20,20,0], [20,0,0], [0,0,0], [0,20,0], [20,20,20], [0,20,20], [0,0,20], [20,0,20] ], @@ -17,12 +14,10 @@ my $cube = { { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); - $m->repair; my ($vertices, $facets) = ($m->vertices, $m->facets); is_deeply $vertices, $cube->{vertices}, 'vertices arrayref roundtrip'; is_deeply $facets, $cube->{facets}, 'facets arrayref roundtrip'; - is scalar(@{$m->normals}), scalar(@$facets), 'normals returns the right number of items'; { my $m2 = $m->clone; @@ -34,109 +29,6 @@ my $cube = { { my $stats = $m->stats; is $stats->{number_of_facets}, scalar(@{ $cube->{facets} }), 'stats.number_of_facets'; - ok abs($stats->{volume} - 20*20*20) < 1E-2, 'stats.volume'; - } - - $m->scale(2); - ok abs($m->stats->{volume} - 40*40*40) < 1E-2, 'scale'; - - $m->scale_xyz(Slic3r::Pointf3->new(2,1,1)); - ok abs($m->stats->{volume} - 2*40*40*40) < 1E-2, 'scale_xyz'; - - $m->translate(5,10,0); - is_deeply $m->vertices->[0], [85,50,0], 'translate'; - - $m->align_to_origin; - is_deeply $m->vertices->[2], [0,0,0], 'align_to_origin'; - - is_deeply $m->size, [80,40,40], 'size'; - - $m->scale_xyz(Slic3r::Pointf3->new(0.5,1,1)); - $m->rotate(45, Slic3r::Point->new(20,20)); - ok abs($m->size->[0] - sqrt(2)*40) < 1E-4, 'rotate'; - - { - my $meshes = $m->split; - is scalar(@$meshes), 1, 'split'; - isa_ok $meshes->[0], 'Slic3r::TriangleMesh', 'split'; - is_deeply $m->bb3, $meshes->[0]->bb3, 'split populates stats'; - } - - my $m2 = Slic3r::TriangleMesh->new; - $m2->ReadFromPerl($cube->{vertices}, $cube->{facets}); - $m2->repair; - $m->merge($m2); - $m->repair; - is $m->stats->{number_of_facets}, 2 * $m2->stats->{number_of_facets}, 'merge'; - - { - my $meshes = $m->split; - is scalar(@$meshes), 2, 'split'; - } -} - -{ - my $m = Slic3r::TriangleMesh->new; - $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); - $m->repair; - # The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be - # open intervals at the bottom end, closed at the top end. - my @z = (0.0001,2,4,8,6,8,10,12,14,16,18,20); - my $result = $m->slice(\@z); - my $SCALING_FACTOR = 0.000001; - for my $i (0..$#z) { - is scalar(@{$result->[$i]}), 1, "number of returned polygons per layer (z = " . $z[$i] . ")"; - is $result->[$i][0]->area, 20*20/($SCALING_FACTOR**2), 'size of returned polygon'; - } -} - -{ - my $m = Slic3r::TriangleMesh->new; - $m->ReadFromPerl( - [ [0,0,0],[0,0,20],[0,5,0],[0,5,20],[50,0,0],[50,0,20],[15,5,0],[35,5,0],[15,20,0],[50,5,0],[35,20,0],[15,5,10],[50,5,20],[35,5,10],[35,20,10],[15,20,10] ], - [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[0,2,4],[4,2,6],[7,6,8],[4,6,7],[9,4,7],[7,8,10],[2,3,6],[11,3,12],[7,12,9],[13,12,7],[6,3,11],[11,12,13],[3,1,5],[12,3,5],[5,4,9],[12,5,9],[13,7,10],[14,13,10],[8,15,10],[10,15,14],[6,11,8],[8,11,15],[15,11,13],[14,15,13] ], - ); - $m->repair; - { - # at Z = 10 we have a top horizontal surface - my $slices = $m->slice([ 5, 10 ]); - is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a top tangent plane includes its area'; - } - $m->mirror_z; - { - # this second test also checks that performing a second slice on a mesh after - # a transformation works properly (shared_vertices is correctly invalidated); - # at Z = -10 we have a bottom horizontal surface - # (The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be - # open intervals at the bottom end, closed at the top end, so the Z = -10 is shifted a bit up to get a valid slice). - my $slices = $m->slice([ -5, -10+0.00001 ]); - is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area'; - } -} - -{ - my $m = Slic3r::TriangleMesh->new; - $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); - $m->repair; - { - my $upper = Slic3r::TriangleMesh->new; - my $lower = Slic3r::TriangleMesh->new; - $m->cut(0, $upper, $lower); - $upper->repair; $lower->repair; - is $upper->facets_count, 12, 'upper mesh has all facets except those belonging to the slicing plane'; - is $lower->facets_count, 0, 'lower mesh has no facets'; - } - { - my $upper = Slic3r::TriangleMesh->new; - my $lower = Slic3r::TriangleMesh->new; - $m->cut(10, $upper, $lower); - #$upper->repair; $lower->repair; - # we expect: - # 2 facets on external horizontal surfaces - # 3 facets on each side = 12 facets - # 6 facets on the triangulated side (8 vertices) - is $upper->facets_count, 2+12+6, 'upper mesh has the expected number of facets'; - is $lower->facets_count, 2+12+6, 'lower mesh has the expected number of facets'; } } diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 35fbb48ee3..34795681ef 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -88,8 +88,6 @@ bool looks_like_multipart_object() const; void convert_multipart_object(unsigned int max_extruders); - void print_info() const; - bool store_stl(char *path, bool binary) %code%{ TriangleMesh mesh = THIS->mesh(); RETVAL = Slic3r::store_stl(path, &mesh, binary); %}; @@ -212,7 +210,6 @@ ModelMaterial::attributes() %code%{ THIS->origin_translation = *point; %}; void ensure_on_bed(); - bool needed_repair() const; int materials_count() const; int facets_count(); void center_around_origin(); @@ -223,13 +220,6 @@ ModelMaterial::attributes() %code{% THIS->rotate(angle, *axis); %}; void mirror(Axis axis); - ModelObjectPtrs* split_object() - %code%{ - RETVAL = new ModelObjectPtrs(); // leak? - THIS->split(RETVAL); - %}; - - void print_info() const; }; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 2b07c78eef..105fe0f05b 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -11,14 +11,11 @@ ~TriangleMesh(); Clone clone() %code{% RETVAL = THIS; %}; - void ReadSTLFile(char* input_file); void write_ascii(char* output_file); void write_binary(char* output_file); - void repair(); - void WriteOBJFile(char* output_file); void scale(float factor); void scale_xyz(Vec3d* versor) - %code{% THIS->scale(*versor); %}; + %code{% THIS->scale(versor->cast()); %}; void translate(float x, float y, float z); void rotate_x(float angle); void rotate_y(float angle); @@ -28,16 +25,13 @@ void mirror_z(); void align_to_origin(); void rotate(double angle, Point* center); - TriangleMeshPtrs split(); void merge(TriangleMesh* mesh) %code{% THIS->merge(*mesh); %}; - ExPolygons horizontal_projection(); Clone convex_hull(); Clone bounding_box(); Clone center() %code{% RETVAL = THIS->bounding_box().center(); %}; int facets_count(); - void reset_repair_stats(); %{ @@ -46,51 +40,40 @@ TriangleMesh::ReadFromPerl(vertices, facets) SV* vertices SV* facets CODE: - stl_file &stl = THIS->stl; - stl.stats.type = inmemory; - - // count facets and allocate memory - AV* facets_av = (AV*)SvRV(facets); - stl.stats.number_of_facets = av_len(facets_av)+1; - stl.stats.original_num_facets = stl.stats.number_of_facets; - stl_allocate(&stl); - - // read geometry - AV* vertices_av = (AV*)SvRV(vertices); - for (int i = 0; i < stl.stats.number_of_facets; i++) { - AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0)); - stl_facet facet; - facet.normal(0) = 0; - facet.normal(1) = 0; - facet.normal(2) = 0; - for (unsigned int v = 0; v <= 2; v++) { - AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0)); - facet.vertex[v](0) = SvNV(*av_fetch(vertex_av, 0, 0)); - facet.vertex[v](1) = SvNV(*av_fetch(vertex_av, 1, 0)); - facet.vertex[v](2) = SvNV(*av_fetch(vertex_av, 2, 0)); + std::vector out_vertices; + { + AV* vertices_av = (AV*)SvRV(vertices); + int number_of_vertices = av_len(vertices_av) + 1; + out_vertices.reserve(number_of_vertices); + for (int i = 0; i < number_of_vertices; ++ i) { + AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, i, 0)); + out_vertices.push_back(Slic3r::Vec3f(SvNV(*av_fetch(vertex_av, 0, 0)), SvNV(*av_fetch(vertex_av, 1, 0)), SvNV(*av_fetch(vertex_av, 2, 0)))); } - facet.extra[0] = 0; - facet.extra[1] = 0; - - stl.facet_start[i] = facet; } - - stl_get_size(&stl); + std::vector out_indices; + { + AV* facets_av = (AV*)SvRV(facets); + int number_of_facets = av_len(facets_av) + 1; + out_indices.reserve(number_of_facets); + for (int i = 0; i < number_of_facets; ++ i) { + AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0)); + out_indices.push_back(Slic3r::Vec3i(SvIV(*av_fetch(facet_av, 0, 0)), SvIV(*av_fetch(facet_av, 1, 0)), SvIV(*av_fetch(facet_av, 2, 0)))); + } + } + *THIS = TriangleMesh(std::move(out_vertices), std::move(out_indices)); SV* TriangleMesh::stats() CODE: HV* hv = newHV(); - (void)hv_stores( hv, "number_of_facets", newSViv(THIS->stl.stats.number_of_facets) ); - (void)hv_stores( hv, "number_of_parts", newSViv(THIS->stl.stats.number_of_parts) ); - (void)hv_stores( hv, "volume", newSVnv(THIS->stl.stats.volume) ); - (void)hv_stores( hv, "degenerate_facets", newSViv(THIS->stl.stats.degenerate_facets) ); - (void)hv_stores( hv, "edges_fixed", newSViv(THIS->stl.stats.edges_fixed) ); - (void)hv_stores( hv, "facets_removed", newSViv(THIS->stl.stats.facets_removed) ); - (void)hv_stores( hv, "facets_added", newSViv(THIS->stl.stats.facets_added) ); - (void)hv_stores( hv, "facets_reversed", newSViv(THIS->stl.stats.facets_reversed) ); - (void)hv_stores( hv, "backwards_edges", newSViv(THIS->stl.stats.backwards_edges) ); - (void)hv_stores( hv, "normals_fixed", newSViv(THIS->stl.stats.normals_fixed) ); + (void)hv_stores( hv, "number_of_facets", newSViv(THIS->facets_count()) ); + (void)hv_stores( hv, "number_of_parts", newSViv(THIS->stats().number_of_parts) ); + (void)hv_stores( hv, "volume", newSVnv(THIS->stats().volume) ); + (void)hv_stores( hv, "degenerate_facets", newSViv(THIS->stats().degenerate_facets) ); + (void)hv_stores( hv, "edges_fixed", newSViv(THIS->stats().edges_fixed) ); + (void)hv_stores( hv, "facets_removed", newSViv(THIS->stats().facets_removed) ); + (void)hv_stores( hv, "facets_reversed", newSViv(THIS->stats().facets_reversed) ); + (void)hv_stores( hv, "backwards_edges", newSViv(THIS->stats().backwards_edges) ); RETVAL = (SV*)newRV_noinc((SV*)hv); OUTPUT: RETVAL @@ -98,9 +81,6 @@ TriangleMesh::stats() SV* TriangleMesh::vertices() CODE: - if (!THIS->repaired) CONFESS("vertices() requires repair()"); - THIS->require_shared_vertices(); - // vertices AV* vertices = newAV(); av_extend(vertices, THIS->its.vertices.size()); @@ -120,13 +100,10 @@ TriangleMesh::vertices() SV* TriangleMesh::facets() CODE: - if (!THIS->repaired) CONFESS("facets() requires repair()"); - THIS->require_shared_vertices(); - // facets AV* facets = newAV(); - av_extend(facets, THIS->stl.stats.number_of_facets); - for (int i = 0; i < THIS->stl.stats.number_of_facets; i++) { + av_extend(facets, THIS->facets_count()); + for (int i = 0; i < THIS->facets_count(); i++) { AV* facet = newAV(); av_store(facets, i, newRV_noinc((SV*)facet)); av_extend(facet, 2); @@ -139,35 +116,14 @@ TriangleMesh::facets() OUTPUT: RETVAL -SV* -TriangleMesh::normals() - CODE: - if (!THIS->repaired) CONFESS("normals() requires repair()"); - - // normals - AV* normals = newAV(); - av_extend(normals, THIS->stl.stats.number_of_facets); - for (int i = 0; i < THIS->stl.stats.number_of_facets; i++) { - AV* facet = newAV(); - av_store(normals, i, newRV_noinc((SV*)facet)); - av_extend(facet, 2); - av_store(facet, 0, newSVnv(THIS->stl.facet_start[i].normal(0))); - av_store(facet, 1, newSVnv(THIS->stl.facet_start[i].normal(1))); - av_store(facet, 2, newSVnv(THIS->stl.facet_start[i].normal(2))); - } - - RETVAL = newRV_noinc((SV*)normals); - OUTPUT: - RETVAL - SV* TriangleMesh::size() CODE: AV* size = newAV(); av_extend(size, 2); - av_store(size, 0, newSVnv(THIS->stl.stats.size(0))); - av_store(size, 1, newSVnv(THIS->stl.stats.size(1))); - av_store(size, 2, newSVnv(THIS->stl.stats.size(2))); + av_store(size, 0, newSVnv(THIS->stats().size(0))); + av_store(size, 1, newSVnv(THIS->stats().size(1))); + av_store(size, 2, newSVnv(THIS->stats().size(2))); RETVAL = newRV_noinc((SV*)size); OUTPUT: RETVAL @@ -176,8 +132,6 @@ SV* TriangleMesh::slice(z) std::vector z CODE: - THIS->require_shared_vertices(); // TriangleMeshSlicer needs this - // convert doubles to floats std::vector z_f = cast(z); @@ -206,7 +160,6 @@ TriangleMesh::cut(z, upper_mesh, lower_mesh) TriangleMesh* upper_mesh; TriangleMesh* lower_mesh; CODE: - THIS->require_shared_vertices(); // TriangleMeshSlicer needs this indexed_triangle_set upper, lower; cut_mesh(THIS->its, z, upper_mesh ? &upper : nullptr, lower_mesh ? &lower : nullptr); if (upper_mesh) @@ -217,12 +170,12 @@ TriangleMesh::cut(z, upper_mesh, lower_mesh) std::vector TriangleMesh::bb3() CODE: - RETVAL.push_back(THIS->stl.stats.min(0)); - RETVAL.push_back(THIS->stl.stats.min(1)); - RETVAL.push_back(THIS->stl.stats.max(0)); - RETVAL.push_back(THIS->stl.stats.max(1)); - RETVAL.push_back(THIS->stl.stats.min(2)); - RETVAL.push_back(THIS->stl.stats.max(2)); + RETVAL.push_back(THIS->stats().min(0)); + RETVAL.push_back(THIS->stats().min(1)); + RETVAL.push_back(THIS->stats().max(0)); + RETVAL.push_back(THIS->stats().max(1)); + RETVAL.push_back(THIS->stats().min(2)); + RETVAL.push_back(THIS->stats().max(2)); OUTPUT: RETVAL @@ -250,16 +203,3 @@ sphere(double rho) %} }; - -%package{Slic3r::TriangleMesh}; - -%{ -PROTOTYPES: DISABLE - -std::string -hello_world() - CODE: - RETVAL = "Hello world!"; - OUTPUT: - RETVAL -%} diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 54e686ae36..ca26750dc5 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -239,7 +239,6 @@ SupportLayerPtrs* T_PTR_ARRAYREF_PTR # we return these types whenever we want the items to be returned # by reference and not marked ::Ref because they're newly allocated # and not referenced by any Perl object -TriangleMeshPtrs T_PTR_ARRAYREF INPUT diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 2d364628ec..f9e61c6a03 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -163,7 +163,6 @@ %typemap{Surfaces}; %typemap{Polygons*}; %typemap{TriangleMesh*}; -%typemap{TriangleMeshPtrs}; %typemap{Model*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; From 40e4116d9a37ca53af8eb32260b4ab0165efcdcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 21 Sep 2021 10:45:57 +0200 Subject: [PATCH 03/25] Added a missing include (GCC 11.1). --- src/libslic3r/TriangleMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 1e41c1be57..183b195a10 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include From 7e3306c68f9e4ab381e798e15b314a9c2b794ae3 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 21 Sep 2021 11:07:33 +0200 Subject: [PATCH 04/25] Fixed triangulation of meshes split by the cut tool. --- src/libslic3r/Point.hpp | 4 +-- src/libslic3r/TriangleMeshSlicer.cpp | 38 +++++++++++++++++++++------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 20726270d8..21eb48c2ee 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -418,7 +418,7 @@ template> inline constexpr Tout unscaled(const Tin &v) noexcept { - return Tout(v * Tout(SCALING_FACTOR)); + return Tout(v) * Tout(SCALING_FACTOR); } // Unscaling for Eigen vectors. Input base type can be arithmetic, output base @@ -432,7 +432,7 @@ template unscaled(const Eigen::Matrix &v) noexcept { - return v.template cast() * SCALING_FACTOR; + return v.template cast() * Tout(SCALING_FACTOR); } // Align a coordinate to a grid. The coordinate may be negative, diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index dd11420bbd..d3c9a49b5c 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1989,7 +1989,7 @@ static void triangulate_slice( std::vector map_duplicate_vertex(int(its.vertices.size()) - num_original_vertices, -1); int i = 0; int k = 0; - for (; i < int(map_vertex_to_index.size()); ++ i) { + for (; i < int(map_vertex_to_index.size());) { map_vertex_to_index[k ++] = map_vertex_to_index[i]; const Vec2f &ipos = map_vertex_to_index[i].first; const int iidx = map_vertex_to_index[i].second; @@ -2004,6 +2004,7 @@ static void triangulate_slice( // map to the first vertex map_duplicate_vertex[jidx - num_original_vertices] = iidx; } + i = j; } map_vertex_to_index.erase(map_vertex_to_index.begin() + k, map_vertex_to_index.end()); for (stl_triangle_vertex_indices &f : its.indices) @@ -2097,6 +2098,10 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u lower->indices.reserve(mesh.indices.size()); } +#ifndef NDEBUG + size_t num_open_edges_old = triangulate_caps ? its_num_open_edges(mesh) : 0; +#endif // NDEBUG + // To triangulate the caps after slicing. IntersectionLines upper_lines, lower_lines; std::vector upper_slice_vertices, lower_slice_vertices; @@ -2163,13 +2168,14 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u // get vertices starting from the isolated one int iv = isolated_vertex; stl_vertex v0v1, v2v0; - assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id ||facets_edge_ids[facet_idx](iv) == line.edge_b_id); + assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id || facets_edge_ids[facet_idx](iv) == line.edge_b_id); if (facets_edge_ids[facet_idx](iv) == line.edge_a_id) { - v0v1 = to_3d(unscaled(line.a), z); - v2v0 = to_3d(unscaled(line.b), z); + // Unscale to doubles first, then to floats to reach the same accuracy as triangulate_expolygons_2d(). + v0v1 = to_3d(unscaled(line.a).cast().eval(), z); + v2v0 = to_3d(unscaled(line.b).cast().eval(), z); } else { - v0v1 = to_3d(unscaled(line.b), z); - v2v0 = to_3d(unscaled(line.a), z); + v0v1 = to_3d(unscaled(line.b).cast().eval(), z); + v2v0 = to_3d(unscaled(line.a).cast().eval(), z); } const stl_vertex &v0 = vertices[iv]; const int iv0 = facet[iv]; @@ -2223,11 +2229,25 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u } } - if (upper != nullptr) + if (upper != nullptr) { triangulate_slice(*upper, upper_lines, upper_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps, NORMALS_DOWN); +#ifndef NDEBUG + if (triangulate_caps) { + size_t num_open_edges_new = its_num_open_edges(*upper); + assert(num_open_edges_new <= num_open_edges_old); + } +#endif // NDEBUG + } - if (lower != nullptr) + if (lower != nullptr) { triangulate_slice(*lower, lower_lines, lower_slice_vertices, int(mesh.vertices.size()), z, triangulate_caps, NORMALS_UP); +#ifndef NDEBUG + if (triangulate_caps) { + size_t num_open_edges_new = its_num_open_edges(*lower); + assert(num_open_edges_new <= num_open_edges_old); + } +#endif // NDEBUG + } } -} +} // namespace Slic3r From 63647f594e67d8582454b67b985d9d0904eca6a1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 21 Sep 2021 12:34:40 +0200 Subject: [PATCH 05/25] Add dedicated subclass NotificationProgressIndicator to replace ProgressStatusBar and revert changes from b9dab754, keep UI jobs untouched --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_App.cpp | 2 +- src/slic3r/GUI/GUI_App.hpp | 2 +- src/slic3r/GUI/GUI_Preview.cpp | 2 +- src/slic3r/GUI/Jobs/ArrangeJob.hpp | 17 +++-- src/slic3r/GUI/Jobs/FillBedJob.hpp | 5 +- src/slic3r/GUI/Jobs/Job.cpp | 48 +++++++------- src/slic3r/GUI/Jobs/Job.hpp | 7 +- .../Jobs/NotificationProgressIndicator.cpp | 33 ++++++++++ .../Jobs/NotificationProgressIndicator.hpp | 26 ++++++++ src/slic3r/GUI/Jobs/PlaterJob.hpp | 5 +- src/slic3r/GUI/Jobs/RotoptimizeJob.hpp | 6 +- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 65 +++++++++---------- src/slic3r/GUI/Jobs/SLAImportJob.hpp | 8 +-- src/slic3r/GUI/Plater.cpp | 28 +++++--- src/slic3r/GUI/Plater.hpp | 4 +- 16 files changed, 161 insertions(+), 99 deletions(-) create mode 100644 src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp create mode 100644 src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 34f1a188d2..29d1fae293 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -179,6 +179,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/SLAImportJob.hpp GUI/Jobs/SLAImportJob.cpp GUI/Jobs/ProgressIndicator.hpp + GUI/Jobs/NotificationProgressIndicator.hpp + GUI/Jobs/NotificationProgressIndicator.cpp GUI/ProgressStatusBar.hpp GUI/ProgressStatusBar.cpp GUI/Mouse3DController.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 2c9aabc38e..23ce889dfc 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2279,7 +2279,7 @@ wxBookCtrlBase* GUI_App::tab_panel() const return mainframe->m_tabpanel; } -std::shared_ptr GUI_App::notification_manager() +NotificationManager * GUI_App::notification_manager() { return plater_->get_notification_manager(); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 2af4a3a4ae..3171f3b035 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -275,7 +275,7 @@ public: ObjectLayers* obj_layers(); Plater* plater(); Model& model(); - std::shared_ptr notification_manager(); + NotificationManager * notification_manager(); // Parameters extracted from the command line to be passed to GUI after initialization. GUI_InitParams* init_params { nullptr }; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index f5b29153e1..bbe54c2ab5 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -725,7 +725,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); if( bottom_area - top_area > delta_area) { - std::shared_ptr notif_mngr = wxGetApp().plater()->get_notification_manager(); + NotificationManager *notif_mngr = wxGetApp().plater()->get_notification_manager(); notif_mngr->push_notification( NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp index 2ccb7a04f1..a5ecc0c834 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.hpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.hpp @@ -9,7 +9,6 @@ namespace Slic3r { class ModelInstance; namespace GUI { -class NotificationManager; class ArrangeJob : public PlaterJob { @@ -18,21 +17,21 @@ class ArrangeJob : public PlaterJob ArrangePolygons m_selected, m_unselected, m_unprintable; std::vector m_unarranged; - + // clear m_selected and m_unselected, reserve space for next usage void clear_input(); // Prepare all objects on the bed regardless of the selection void prepare_all(); - + // Prepare the selected and unselected items separately. If nothing is // selected, behaves as if everything would be selected. void prepare_selected(); ArrangePolygon get_arrange_poly_(ModelInstance *mi); - + protected: - + void prepare() override; void on_exception(const std::exception_ptr &) override; @@ -40,15 +39,15 @@ protected: void process() override; public: - ArrangeJob(std::shared_ptr nm, Plater *plater) - : PlaterJob{nm, plater} + ArrangeJob(std::shared_ptr pri, Plater *plater) + : PlaterJob{std::move(pri), plater} {} - + int status_range() const override { return int(m_selected.size() + m_unprintable.size()); } - + void finalize() override; }; diff --git a/src/slic3r/GUI/Jobs/FillBedJob.hpp b/src/slic3r/GUI/Jobs/FillBedJob.hpp index 548974fa6b..bf407656d1 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.hpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.hpp @@ -6,7 +6,6 @@ namespace Slic3r { namespace GUI { class Plater; -class NotificationManager; class FillBedJob : public PlaterJob { @@ -28,8 +27,8 @@ protected: void process() override; public: - FillBedJob(std::shared_ptr nm, Plater *plater) - : PlaterJob{nm, plater} + FillBedJob(std::shared_ptr pri, Plater *plater) + : PlaterJob{std::move(pri), plater} {} int status_range() const override diff --git a/src/slic3r/GUI/Jobs/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp index c198de40ca..7590f2d420 100644 --- a/src/slic3r/GUI/Jobs/Job.cpp +++ b/src/slic3r/GUI/Jobs/Job.cpp @@ -2,11 +2,9 @@ #include #include "Job.hpp" -#include "../NotificationManager.hpp" #include #include - namespace Slic3r { void GUI::Job::run(std::exception_ptr &eptr) @@ -19,7 +17,7 @@ void GUI::Job::run(std::exception_ptr &eptr) } m_running.store(false); - + // ensure to call the last status to finalize the job update_status(status_range(), ""); } @@ -32,8 +30,8 @@ void GUI::Job::update_status(int st, const wxString &msg) wxQueueEvent(this, evt); } -GUI::Job::Job(std::shared_ptr nm) - : m_notifications(nm) +GUI::Job::Job(std::shared_ptr pri) + : m_progress(std::move(pri)) { m_thread_evt_id = wxNewId(); @@ -42,21 +40,21 @@ GUI::Job::Job(std::shared_ptr nm) auto msg = evt.GetString(); if (!msg.empty() && !m_worker_error) - m_notifications->progress_indicator_set_status_text(msg.ToUTF8().data()); + m_progress->set_status_text(msg.ToUTF8().data()); if (m_finalized) return; - m_notifications->progress_indicator_set_progress(evt.GetInt()); + m_progress->set_progress(evt.GetInt()); if (evt.GetInt() == status_range() || m_worker_error) { // set back the original range and cancel callback - m_notifications->progress_indicator_set_range(m_range); - m_notifications->progress_indicator_set_cancel_callback(); + m_progress->set_range(m_range); + m_progress->set_cancel_callback(); wxEndBusyCursor(); - + if (m_worker_error) { m_finalized = true; - m_notifications->progress_indicator_set_status_text(""); - m_notifications->progress_indicator_set_progress(m_range); + m_progress->set_status_text(""); + m_progress->set_progress(m_range); on_exception(m_worker_error); } else { @@ -86,22 +84,22 @@ void GUI::Job::start() { // Start the job. No effect if the job is already running if (!m_running.load()) { prepare(); - + // Save the current status indicatior range and push the new one - m_range = m_notifications->progress_indicator_get_range(); - m_notifications->progress_indicator_set_range(status_range()); - + m_range = m_progress->get_range(); + m_progress->set_range(status_range()); + // init cancellation flag and set the cancel callback m_canceled.store(false); - m_notifications->progress_indicator_set_cancel_callback( + m_progress->set_cancel_callback( [this]() { m_canceled.store(true); }); - + m_finalized = false; m_finalizing = false; - + // Changing cursor to busy wxBeginBusyCursor(); - + try { // Execute the job m_worker_error = nullptr; m_thread = create_thread([this] { this->run(m_worker_error); }); @@ -110,7 +108,7 @@ void GUI::Job::start() _(L("ERROR: not enough resources to " "execute a new job."))); } - + // The state changes will be undone when the process hits the // last status value, in the status update handler (see ctor) } @@ -119,12 +117,12 @@ void GUI::Job::start() bool GUI::Job::join(int timeout_ms) { if (!m_thread.joinable()) return true; - + if (timeout_ms <= 0) m_thread.join(); else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) return false; - + return true; } @@ -137,10 +135,10 @@ void GUI::ExclusiveJobGroup::start(size_t jid) { void GUI::ExclusiveJobGroup::join_all(int wait_ms) { std::vector aborted(m_jobs.size(), false); - + for (size_t jid = 0; jid < m_jobs.size(); ++jid) aborted[jid] = m_jobs[jid]->join(wait_ms); - + if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; })) BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; } diff --git a/src/slic3r/GUI/Jobs/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp index ed80d8b5fc..8243ce9430 100644 --- a/src/slic3r/GUI/Jobs/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -8,13 +8,14 @@ #include +#include "ProgressIndicator.hpp" + #include #include namespace Slic3r { namespace GUI { -class NotificationManager; // A class to handle UI jobs like arranging and optimizing rotation. // These are not instant jobs, the user has to be informed about their // state in the status progress indicator. On the other hand they are @@ -32,7 +33,7 @@ class Job : public wxEvtHandler boost::thread m_thread; std::atomic m_running{false}, m_canceled{false}; bool m_finalized = false, m_finalizing = false; - std::shared_ptr m_notifications; + std::shared_ptr m_progress; std::exception_ptr m_worker_error = nullptr; void run(std::exception_ptr &); @@ -64,7 +65,7 @@ protected: } public: - Job(std::shared_ptr nm); + Job(std::shared_ptr pri); bool is_finalized() const { return m_finalized; } diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp new file mode 100644 index 0000000000..cb71705687 --- /dev/null +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.cpp @@ -0,0 +1,33 @@ +#include "NotificationProgressIndicator.hpp" +#include "slic3r/GUI/NotificationManager.hpp" + +namespace Slic3r { namespace GUI { + +NotificationProgressIndicator::NotificationProgressIndicator(NotificationManager *nm): m_nm{nm} {} + +void NotificationProgressIndicator::set_range(int range) +{ + m_nm->progress_indicator_set_range(range); +} + +void NotificationProgressIndicator::set_cancel_callback(CancelFn fn) +{ + m_nm->progress_indicator_set_cancel_callback(std::move(fn)); +} + +void NotificationProgressIndicator::set_progress(int pr) +{ + m_nm->progress_indicator_set_progress(pr); +} + +void NotificationProgressIndicator::set_status_text(const char *msg) +{ + m_nm->progress_indicator_set_status_text(msg); +} + +int NotificationProgressIndicator::get_range() const +{ + return m_nm->progress_indicator_get_range(); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp new file mode 100644 index 0000000000..6b03af69df --- /dev/null +++ b/src/slic3r/GUI/Jobs/NotificationProgressIndicator.hpp @@ -0,0 +1,26 @@ +#ifndef NOTIFICATIONPROGRESSINDICATOR_HPP +#define NOTIFICATIONPROGRESSINDICATOR_HPP + +#include "ProgressIndicator.hpp" + +namespace Slic3r { namespace GUI { + +class NotificationManager; + +class NotificationProgressIndicator: public ProgressIndicator { + NotificationManager *m_nm = nullptr; + +public: + + explicit NotificationProgressIndicator(NotificationManager *nm); + + void set_range(int range) override; + void set_cancel_callback(CancelFn = CancelFn()) override; + void set_progress(int pr) override; + void set_status_text(const char *) override; // utf8 char array + int get_range() const override; +}; + +}} // namespace Slic3r::GUI + +#endif // NOTIFICATIONPROGRESSINDICATOR_HPP diff --git a/src/slic3r/GUI/Jobs/PlaterJob.hpp b/src/slic3r/GUI/Jobs/PlaterJob.hpp index 52e5741aa4..fcf0a54b80 100644 --- a/src/slic3r/GUI/Jobs/PlaterJob.hpp +++ b/src/slic3r/GUI/Jobs/PlaterJob.hpp @@ -6,7 +6,6 @@ namespace Slic3r { namespace GUI { class Plater; -class NotificationManager; class PlaterJob : public Job { protected: @@ -16,8 +15,8 @@ protected: public: - PlaterJob(std::shared_ptr nm, Plater *plater): - Job{nm}, m_plater{plater} {} + PlaterJob(std::shared_ptr pri, Plater *plater): + Job{std::move(pri)}, m_plater{plater} {} }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp index f967bb7a37..cdb367f23a 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp @@ -10,8 +10,6 @@ namespace Slic3r { namespace GUI { -class NotificationManager; - class RotoptimizeJob : public PlaterJob { using FindFn = std::function nm, Plater *plater) - : PlaterJob{nm, plater} + RotoptimizeJob(std::shared_ptr pri, Plater *plater) + : PlaterJob{std::move(pri), plater} {} void finalize() override; diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 9b9151f773..e632028622 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -6,7 +6,6 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" -#include "slic3r/GUI/NotificationManager.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/PresetBundle.hpp" @@ -20,79 +19,79 @@ namespace Slic3r { namespace GUI { enum class Sel { modelAndProfile, profileOnly, modelOnly}; - + class ImportDlg: public wxDialog { wxFilePickerCtrl *m_filepicker; wxComboBox *m_import_dropdown, *m_quality_dropdown; - + public: ImportDlg(Plater *plater) : wxDialog{plater, wxID_ANY, "Import SLA archive"} { auto szvert = new wxBoxSizer{wxVERTICAL}; auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; - + m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), "SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP", wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); - + szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER); szfilepck->Add(m_filepicker, 1); szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); - + auto szchoices = new wxBoxSizer{wxHORIZONTAL}; - + static const std::vector inp_choices = { _(L("Import model and profile")), _(L("Import profile only")), _(L("Import model only")) }; - + m_import_dropdown = new wxComboBox( this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); - + szchoices->Add(m_import_dropdown); szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5); - + static const std::vector qual_choices = { _(L("Accurate")), _(L("Balanced")), _(L("Quick")) }; - + m_quality_dropdown = new wxComboBox( this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); szchoices->Add(m_quality_dropdown); - + m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { if (get_selection() == Sel::profileOnly) m_quality_dropdown->Disable(); else m_quality_dropdown->Enable(); }); - + szvert->Add(szchoices, 0, wxALL, 5); szvert->AddStretchSpacer(1); auto szbtn = new wxBoxSizer(wxHORIZONTAL); szbtn->Add(new wxButton{this, wxID_CANCEL}); szbtn->Add(new wxButton{this, wxID_OK}); szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); - + SetSizerAndFit(szvert); } - + Sel get_selection() const { int sel = m_import_dropdown->GetSelection(); return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); } - + Vec2i get_marchsq_windowsize() const { enum { Accurate, Balanced, Fast}; - + switch(m_quality_dropdown->GetSelection()) { case Fast: return {8, 8}; @@ -102,7 +101,7 @@ public: return {2, 2}; } } - + wxString get_path() const { return m_filepicker->GetPath(); @@ -112,7 +111,7 @@ public: class SLAImportJob::priv { public: Plater *plater; - + Sel sel = Sel::modelAndProfile; indexed_triangle_set mesh; @@ -125,8 +124,8 @@ public: priv(Plater *plt) : plater{plt} {} }; -SLAImportJob::SLAImportJob(std::shared_ptr nm, Plater *plater) - : PlaterJob{nm, plater}, p{std::make_unique(plater)} +SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) + : PlaterJob{std::move(pri), plater}, p{std::make_unique(plater)} {} SLAImportJob::~SLAImportJob() = default; @@ -138,9 +137,9 @@ void SLAImportJob::process() update_status(int(s), _(L("Importing SLA archive"))); return !was_canceled(); }; - + if (p->path.empty()) return; - + std::string path = p->path.ToUTF8().data(); try { switch (p->sel) { @@ -154,11 +153,11 @@ void SLAImportJob::process() p->config_substitutions = import_sla_archive(path, p->profile); break; } - + } catch (std::exception &ex) { p->err = ex.what(); } - + update_status(100, was_canceled() ? _(L("Importing canceled.")) : _(L("Importing done."))); } @@ -175,9 +174,9 @@ void SLAImportJob::reset() void SLAImportJob::prepare() { reset(); - + ImportDlg dlg{p->plater}; - + if (dlg.ShowModal() == wxID_OK) { auto path = dlg.get_path(); auto nm = wxFileName(path); @@ -194,15 +193,15 @@ void SLAImportJob::finalize() { // Ignore the arrange result if aborted. if (was_canceled()) return; - + if (!p->err.empty()) { show_error(p->plater, p->err); p->err = ""; return; } - + std::string name = wxFileName(p->path).GetName().ToUTF8().data(); - + if (!p->profile.empty()) { const ModelObjectPtrs& objects = p->plater->model().objects; for (auto object : objects) @@ -214,15 +213,15 @@ void SLAImportJob::finalize() _(L("Attention!")) ); return; } - + DynamicPrintConfig config = {}; config.apply(SLAFullPrintConfig::defaults()); config += std::move(p->profile); - + wxGetApp().preset_bundle->load_config_model(name, std::move(config)); wxGetApp().load_current_presets(); } - + if (!p->mesh.empty()) { bool is_centered = false; p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{p->mesh}, diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp index 7bc4d5e7e3..c2ca10ef69 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.hpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -5,20 +5,18 @@ namespace Slic3r { namespace GUI { -class NotificationManager; - class SLAImportJob : public PlaterJob { class priv; - + std::unique_ptr p; - + protected: void prepare() override; void process() override; void finalize() override; public: - SLAImportJob(std::shared_ptr nm, Plater *plater); + SLAImportJob(std::shared_ptr pri, Plater *plater); ~SLAImportJob(); void reset(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 126e889165..89b30a47fc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -67,8 +67,8 @@ #include "Jobs/FillBedJob.hpp" #include "Jobs/RotoptimizeJob.hpp" #include "Jobs/SLAImportJob.hpp" +#include "Jobs/NotificationProgressIndicator.hpp" #include "BackgroundSlicingProcess.hpp" -#include "ProgressStatusBar.hpp" #include "PrintHostDialogs.hpp" #include "ConfigWizard.hpp" #include "../Utils/ASCIIFolding.hpp" @@ -1499,7 +1499,7 @@ struct Plater::priv GLToolbar view_toolbar; GLToolbar collapse_toolbar; Preview *preview; - std::shared_ptr notification_manager; + std::unique_ptr notification_manager; ProjectDirtyStateManager dirty_state; @@ -1514,16 +1514,19 @@ struct Plater::priv { priv *m; size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id; + std::shared_ptr m_pri; void before_start() override { m->background_process.stop(); } public: - Jobs(priv *_m) : m(_m) + Jobs(priv *_m) : + m(_m), + m_pri{std::make_shared(m->notification_manager.get())} { - m_arrange_id = add_job(std::make_unique(m->notification_manager, m->q)); - m_fill_bed_id = add_job(std::make_unique(m->notification_manager, m->q)); - m_rotoptimize_id = add_job(std::make_unique(m->notification_manager, m->q)); - m_sla_import_id = add_job(std::make_unique(m->notification_manager, m->q)); + m_arrange_id = add_job(std::make_unique(m_pri, m->q)); + m_fill_bed_id = add_job(std::make_unique(m_pri, m->q)); + m_rotoptimize_id = add_job(std::make_unique(m_pri, m->q)); + m_sla_import_id = add_job(std::make_unique(m_pri, m->q)); } void arrange() @@ -1847,7 +1850,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "support_material_contact_distance", "support_material_bottom_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) - , notification_manager(std::make_shared(q)) + , notification_manager(std::make_unique(q)) , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") @@ -6640,9 +6643,14 @@ Mouse3DController& Plater::get_mouse3d_controller() return p->mouse3d_controller; } -std::shared_ptr Plater::get_notification_manager() +NotificationManager * Plater::get_notification_manager() { - return p->notification_manager; + return p->notification_manager.get(); +} + +const NotificationManager * Plater::get_notification_manager() const +{ + return p->notification_manager.get(); } void Plater::init_notification_manager() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e37f790fce..5406a8a6ee 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -358,7 +358,9 @@ public: void set_bed_shape() const; void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; - std::shared_ptr get_notification_manager(); + NotificationManager * get_notification_manager(); + const NotificationManager * get_notification_manager() const; + void init_notification_manager(); void bring_instance_forward(); From 116fd0526b80aec4ead5433ede905e20557cbff1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 21 Sep 2021 12:49:18 +0200 Subject: [PATCH 06/25] Enabling PrusaLink in physcal printers dialog for MINI. --- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 519abab43f..22ced12e41 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -532,9 +532,9 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change) const std::vector& models = preset->vendor->models; auto it = std::find_if(models.begin(), models.end(), [model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; }); - if (it != models.end() && it->family == "MK3") + if (it != models.end() && (it->family == "MK3" || it->family == "MINI")) continue; - } else if (!preset->vendor && model_id.rfind("MK3", 0) == 0) { + } else if (!preset->vendor && (boost::ends_with(model_id, "MK3") || boost::ends_with(model_id, "MINI"))) { continue; } From ac7674b85aba7d870882b627c090a010de3c7dc1 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 21 Sep 2021 15:30:37 +0200 Subject: [PATCH 07/25] Fixed visualization of G-code lines in G-code viewer (3D view). Improved speed of parsing external G-code. --- src/libslic3r/GCode/GCodeProcessor.cpp | 107 ++++++++++++++----------- src/libslic3r/GCodeReader.cpp | 95 ++++++++++++++++------ src/libslic3r/GCodeReader.hpp | 14 ++++ src/libslic3r/LocalesUtils.cpp | 2 +- src/libslic3r/LocalesUtils.hpp | 3 +- src/libslic3r/Utils.hpp | 13 +++ src/libslic3r/utils.cpp | 2 +- 7 files changed, 163 insertions(+), 73 deletions(-) diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 64c9ba4280..4e731c8b4e 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -343,18 +343,6 @@ void GCodeProcessor::TimeProcessor::reset() machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } -struct FilePtr { - FilePtr(FILE *f) : f(f) {} - ~FilePtr() { this->close(); } - void close() { - if (this->f) { - ::fclose(this->f); - this->f = nullptr; - } - } - FILE* f = nullptr; -}; - void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector& moves, std::vector& lines_ends) { FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; @@ -755,11 +743,11 @@ void GCodeProcessor::Result::reset() { #endif // ENABLE_GCODE_VIEWER_STATISTICS const std::vector> GCodeProcessor::Producers = { - { EProducer::PrusaSlicer, "PrusaSlicer" }, - { EProducer::Slic3rPE, "Slic3r Prusa Edition" }, - { EProducer::Slic3r, "Slic3r" }, + { EProducer::PrusaSlicer, "generated by PrusaSlicer" }, + { EProducer::Slic3rPE, "generated by Slic3r Prusa Edition" }, + { EProducer::Slic3r, "generated by Slic3r" }, { EProducer::Cura, "Cura_SteamEngine" }, - { EProducer::Simplify3D, "Simplify3D" }, + { EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" }, { EProducer::CraftWare, "CraftWare" }, { EProducer::ideaMaker, "ideaMaker" }, { EProducer::KissSlicer, "KISSlicer" } @@ -1191,6 +1179,16 @@ void GCodeProcessor::reset() #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } +static inline const char* skip_whitespaces(const char *begin, const char *end) { + for (; begin != end && (*begin == ' ' || *begin == '\t'); ++ begin); + return begin; +} + +static inline const char* remove_eols(const char *begin, const char *end) { + for (; begin != end && (*(end - 1) == '\r' || *(end - 1) == '\n'); -- end); + return end; +} + void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) { CNumericLocalesSetter locales_setter; @@ -1202,14 +1200,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function 1 && detect_producer(comment)) + m_parser.parse_file_raw(filename, [this](GCodeReader& reader, const char *begin, const char *end) { + begin = skip_whitespaces(begin, end); + if (begin != end && *begin == ';') { + // Comment. + begin = skip_whitespaces(++ begin, end); + end = remove_eols(begin, end); + if (begin != end && detect_producer(std::string_view(begin, end - begin))) m_parser.quit_parsing(); } - }); + }); m_parser.reset(); // if the gcode was produced by PrusaSlicer, @@ -1374,9 +1374,11 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) }; BedSize bed_size; + bool producer_detected = false; - m_parser.parse_file(filename, [this, &bed_size](GCodeReader& reader, const GCodeReader::GCodeLine& line) { - auto extract_double = [](const std::string& cmt, const std::string& key, double& out) { + m_parser.parse_file_raw(filename, [this, &bed_size, &producer_detected](GCodeReader& reader, const char* begin, const char* end) { + + auto extract_double = [](const std::string_view cmt, const std::string& key, double& out) { size_t pos = cmt.find(key); if (pos != cmt.npos) { pos = cmt.find(',', pos); @@ -1388,12 +1390,12 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) return false; }; - auto extract_floats = [](const std::string& cmt, const std::string& key, std::vector& out) { + auto extract_floats = [](const std::string_view cmt, const std::string& key, std::vector& out) { size_t pos = cmt.find(key); if (pos != cmt.npos) { pos = cmt.find(',', pos); if (pos != cmt.npos) { - std::string data_str = cmt.substr(pos + 1); + const std::string_view data_str = cmt.substr(pos + 1); std::vector values_str; boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on); for (const std::string& s : values_str) { @@ -1404,28 +1406,39 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename) } return false; }; - - const std::string& comment = line.raw(); - if (comment.length() > 2 && comment.front() == ';') { - if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos) - extract_double(comment, "strokeXoverride", bed_size.x); - else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos) - extract_double(comment, "strokeYoverride", bed_size.y); - else if (comment.find("filamentDiameters") != comment.npos) { - m_result.filament_diameters.clear(); - extract_floats(comment, "filamentDiameters", m_result.filament_diameters); + + begin = skip_whitespaces(begin, end); + end = remove_eols(begin, end); + if (begin != end) + if (*begin == ';') { + // Comment. + begin = skip_whitespaces(++ begin, end); + if (begin != end) { + std::string_view comment(begin, end - begin); + if (producer_detected) { + if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos) + extract_double(comment, "strokeXoverride", bed_size.x); + else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos) + extract_double(comment, "strokeYoverride", bed_size.y); + else if (comment.find("filamentDiameters") != comment.npos) { + m_result.filament_diameters.clear(); + extract_floats(comment, "filamentDiameters", m_result.filament_diameters); + } else if (comment.find("filamentDensities") != comment.npos) { + m_result.filament_densities.clear(); + extract_floats(comment, "filamentDensities", m_result.filament_densities); + } else if (comment.find("extruderDiameter") != comment.npos) { + std::vector extruder_diameters; + extract_floats(comment, "extruderDiameter", extruder_diameters); + m_result.extruders_count = extruder_diameters.size(); + } + } else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)")) + producer_detected = true; + } + } else { + // Some non-empty G-code line detected, stop parsing config comments. + reader.quit_parsing(); } - else if (comment.find("filamentDensities") != comment.npos) { - m_result.filament_densities.clear(); - extract_floats(comment, "filamentDensities", m_result.filament_densities); - } - else if (comment.find("extruderDiameter") != comment.npos) { - std::vector extruder_diameters; - extract_floats(comment, "extruderDiameter", extruder_diameters); - m_result.extruders_count = extruder_diameters.size(); - } - } - }); + }); if (m_result.extruders_count == 0) m_result.extruders_count = std::max(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size())); diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 61ab10f223..657f47f0ec 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -5,6 +5,7 @@ #include #include #include +#include "Utils.hpp" #include "LocalesUtils.hpp" @@ -126,44 +127,92 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair +bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback) { - boost::nowide::ifstream f(file); - f.sync_with_stdio(false); + FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; + + // Read the input stream 64kB at a time, extract lines and process them. std::vector buffer(65536 * 10, 0); - std::string line; + // Line buffer. + std::string gcode_line; + size_t file_pos = 0; m_parsing = true; - GCodeLine gline; - while (m_parsing && ! f.eof()) { - f.read(buffer.data(), buffer.size()); - if (! f.eof() && ! f.good()) - // Reading the input file failed. + for (;;) { + size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); + if (::ferror(in.f)) return false; + bool eof = cnt_read == 0; auto it = buffer.begin(); - auto it_bufend = buffer.begin() + f.gcount(); - while (it != it_bufend) { - bool eol = false; + auto it_bufend = buffer.begin() + cnt_read; + while (it != it_bufend || (eof && ! gcode_line.empty())) { + // Find end of line. + bool eol = false; auto it_end = it; - for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ; - eol |= f.eof() && it_end == it_bufend; + for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) + if (*it_end == '\n') + line_end_callback((it_end - buffer.begin()) + 1); + // End of line is indicated also if end of file was reached. + eol |= eof && it_end == it_bufend; if (eol) { - gline.reset(); - if (line.empty()) - this->parse_line(&(*it), &(*it_end), gline, callback); + if (gcode_line.empty()) + parse_line_callback(&(*it), &(*it_end)); else { - line.insert(line.end(), it, it_end); - this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback); - line.clear(); + gcode_line.insert(gcode_line.end(), it, it_end); + parse_line_callback(gcode_line.c_str(), gcode_line.c_str() + gcode_line.size()); + gcode_line.clear(); } + if (! m_parsing) + // The callback wishes to exit. + return true; } else - line.insert(line.end(), it, it_end); - // Skip all the empty lines. - for (it = it_end; it != it_bufend && (*it == '\r' || *it == '\n'); ++ it) ; + gcode_line.insert(gcode_line.end(), it, it_end); + // Skip EOL. + it = it_end; + if (it != it_bufend && *it == '\r') + ++ it; + if (it != it_bufend && *it == '\n') { + line_end_callback((it - buffer.begin()) + 1); + ++ it; + } } + if (eof) + break; + file_pos += cnt_read; } return true; } +template +bool GCodeReader::parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback) +{ + GCodeLine gline; + return this->parse_file_raw_internal(filename, + [this, &gline, parse_line_callback](const char *begin, const char *end) { + gline.reset(); + this->parse_line(begin, end, gline, parse_line_callback); + }, + line_end_callback); +} + +bool GCodeReader::parse_file(const std::string &file, callback_t callback) +{ + return this->parse_file_internal(file, callback, [](size_t){}); +} + +bool GCodeReader::parse_file(const std::string &file, callback_t callback, std::vector &lines_ends) +{ + lines_ends.clear(); + return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos){ lines_ends.emplace_back(file_pos); }); +} + +bool GCodeReader::parse_file_raw(const std::string &filename, raw_line_callback_t line_callback) +{ + return this->parse_file_raw_internal(filename, + [this, line_callback](const char *begin, const char *end) { line_callback(*this, begin, end); }, + [](size_t){}); +} + bool GCodeReader::GCodeLine::has(char axis) const { const char *c = m_raw.c_str(); diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index 15376c0fc6..0ab268139f 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -76,6 +76,7 @@ public: }; typedef std::function callback_t; + typedef std::function raw_line_callback_t; GCodeReader() : m_verbose(false), m_extrusion_axis('E') { this->reset(); } void reset() { memset(m_position, 0, sizeof(m_position)); } @@ -114,6 +115,13 @@ public: // Returns false if reading the file failed. bool parse_file(const std::string &file, callback_t callback); + // Collect positions of line ends in the binary G-code to be used by the G-code viewer when memory mapping and displaying section of G-code + // as an overlay in the 3D scene. + bool parse_file(const std::string &file, callback_t callback, std::vector &lines_ends); + // Just read the G-code file line by line, calls callback (const char *begin, const char *end). Returns false if reading the file failed. + bool parse_file_raw(const std::string &file, raw_line_callback_t callback); + + // To be called by the callback to stop parsing. void quit_parsing() { m_parsing = false; } float& x() { return m_position[X]; } @@ -132,6 +140,11 @@ public: // void set_extrusion_axis(char axis) { m_extrusion_axis = axis; } private: + template + bool parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); + template + bool parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); + const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair &command); void update_coordinates(GCodeLine &gline, std::pair &command); @@ -154,6 +167,7 @@ private: char m_extrusion_axis; float m_position[NUM_AXES]; bool m_verbose; + // To be set by the callback to stop parsing. bool m_parsing{ false }; }; diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp index 8c42a36baf..d321072335 100644 --- a/src/libslic3r/LocalesUtils.cpp +++ b/src/libslic3r/LocalesUtils.cpp @@ -51,7 +51,7 @@ bool is_decimal_separator_point() } -double string_to_double_decimal_point(const std::string& str, size_t* pos /* = nullptr*/) +double string_to_double_decimal_point(const std::string_view str, size_t* pos /* = nullptr*/) { double out; size_t p = fast_float::from_chars(str.data(), str.data() + str.size(), out).ptr - str.data(); diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp index 113cfa04b0..f63c3572f7 100644 --- a/src/libslic3r/LocalesUtils.hpp +++ b/src/libslic3r/LocalesUtils.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef __APPLE__ #include @@ -40,7 +41,7 @@ bool is_decimal_separator_point(); // (We use user C locales and "C" C++ locales in most of the code.) std::string float_to_string_decimal_point(double value, int precision = -1); //std::string float_to_string_decimal_point(float value, int precision = -1); -double string_to_double_decimal_point(const std::string& str, size_t* pos = nullptr); +double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr); } // namespace Slic3r diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index a01e63166b..cfb4cfa92e 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -255,6 +255,19 @@ template struct IsTriviallyCopyable { static constexpr bool value = template struct IsTriviallyCopyable : public std::is_trivially_copyable {}; #endif +// A very lightweight ROII wrapper around C FILE. +// The old C file API is much faster than C++ streams, thus they are recommended for processing large / huge files. +struct FilePtr { + FilePtr(FILE *f) : f(f) {} + ~FilePtr() { this->close(); } + void close() { + if (this->f) { + ::fclose(this->f); + this->f = nullptr; + } + } + FILE* f = nullptr; +}; class ScopeGuard { diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index d63ce1f635..efffcfe247 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -889,7 +889,7 @@ std::string string_printf(const char *format, ...) std::string header_slic3r_generated() { - return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp(); + return std::string("generated by PrusaSlicer " SLIC3R_VERSION " on " ) + Utils::utc_timestamp(); } std::string header_gcodeviewer_generated() From 306b54bc0a78ba768aa7544f117c2884a3af833e Mon Sep 17 00:00:00 2001 From: Justin Schuh Date: Tue, 21 Sep 2021 06:50:04 -0700 Subject: [PATCH 08/25] Fix float accuracy issue when thick_bridges is off (#6957) --- src/libslic3r/PrintObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index d9a4f2670a..74ef27bd74 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1447,7 +1447,7 @@ void PrintObject::bridge_over_infill() Polygons to_bridge_pp = internal_solid; // iterate through lower layers spanned by bridge_flow - double bottom_z = layer->print_z - bridge_flow.height(); + double bottom_z = layer->print_z - bridge_flow.height() - EPSILON; for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) { const Layer* lower_layer = m_layers[i]; From a5a7f64db08bbd1c93c0800b3f96d0e372ca1e40 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 21 Sep 2021 16:03:30 +0200 Subject: [PATCH 09/25] Mesh statistics for imported STLs: Added backwards_edges to open_edges, so that a mesh with incorrectly oriented faces will not be considered a manifold. Added assert for 3D convex hulls constructed by QHull. They shall be manifold (however sometimes they are not). --- src/libslic3r/TriangleMesh.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 183b195a10..73ac2eade5 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -191,7 +191,7 @@ bool TriangleMesh::ReadSTLFile(const char* input_file, bool repair) auto facets_w_1_bad_edge = stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge; auto facets_w_2_bad_edge = stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge; auto facets_w_3_bad_edge = stl.stats.number_of_facets - stl.stats.connected_facets_1_edge; - m_stats.open_edges = facets_w_1_bad_edge + facets_w_2_bad_edge * 2 + facets_w_3_bad_edge * 3; + m_stats.open_edges = stl.stats.backwards_edges + facets_w_1_bad_edge + facets_w_2_bad_edge * 2 + facets_w_3_bad_edge * 3; m_stats.edges_fixed = stl.stats.edges_fixed; m_stats.degenerate_facets = stl.stats.degenerate_facets; @@ -507,7 +507,9 @@ TriangleMesh TriangleMesh::convex_hull_3d() const } } - return TriangleMesh { std::move(dst_vertices), std::move(dst_facets) }; + TriangleMesh mesh{ std::move(dst_vertices), std::move(dst_facets) }; + assert(mesh.stats().manifold()); + return mesh; } std::vector TriangleMesh::slice(const std::vector &z) const From 238e65e45515da63c0a0ea5283b8a5e91ef1353d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 21 Sep 2021 17:18:12 +0200 Subject: [PATCH 10/25] Improvements for statistics information: + Show 2 groups of errors: "Auto-repared" and "Remaning". + Use different icons for volumes with/without remaining errors + Fixed update of the ObjectList warnings icons after switch the color mode of a PrusaSlicer + Some code refactoring: Use same code for tooltips for "exclamation"-icon from an ObjectList and from a Sidebar->ObjectInfo --- resources/icons/exclamation_manifold.svg | 17 +++++ .../icons/white/exclamation_manifold.svg | 17 +++++ src/libslic3r/Model.cpp | 1 + src/slic3r/GUI/GUI_App.cpp | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 54 +++++++++----- src/slic3r/GUI/GUI_ObjectList.hpp | 12 ++-- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 10 ++- src/slic3r/GUI/GUI_ObjectManipulation.hpp | 2 +- src/slic3r/GUI/ObjectDataViewModel.cpp | 70 ++++++++++++------- src/slic3r/GUI/ObjectDataViewModel.hpp | 19 +++-- src/slic3r/GUI/Plater.cpp | 39 +++++------ 11 files changed, 163 insertions(+), 82 deletions(-) create mode 100644 resources/icons/exclamation_manifold.svg create mode 100644 resources/icons/white/exclamation_manifold.svg diff --git a/resources/icons/exclamation_manifold.svg b/resources/icons/exclamation_manifold.svg new file mode 100644 index 0000000000..cd8ba59544 --- /dev/null +++ b/resources/icons/exclamation_manifold.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/white/exclamation_manifold.svg b/resources/icons/white/exclamation_manifold.svg new file mode 100644 index 0000000000..a18590167c --- /dev/null +++ b/resources/icons/white/exclamation_manifold.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8337ab3846..b42dfb6a4a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1615,6 +1615,7 @@ TriangleMeshStats ModelObject::get_object_stl_stats() const const TriangleMeshStats& stats = volume->mesh().stats(); // initialize full_stats (for repaired errors) + full_stats.open_edges += stats.open_edges; full_stats.degenerate_facets += stats.degenerate_facets; full_stats.edges_fixed += stats.edges_fixed; full_stats.facets_removed += stats.facets_removed; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 23ce889dfc..8689f43cdf 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1403,9 +1403,8 @@ void GUI_App::force_colors_update() void GUI_App::update_ui_from_settings() { update_label_colours(); - mainframe->update_ui_from_settings(); - #ifdef _WIN32 + // Upadte UU colors before Update UI from settings if (m_force_colors_update) { m_force_colors_update = false; mainframe->force_color_changed(); @@ -1414,6 +1413,7 @@ void GUI_App::update_ui_from_settings() m_wizard->force_color_changed(); } #endif + mainframe->update_ui_from_settings(); } void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_maximized) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 35e5bb83e2..7c019337e4 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -382,12 +382,17 @@ int ObjectList::get_mesh_errors_count(const int obj_idx, const int vol_idx /*= - return (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx); } -wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /*= -1*/) const +static std::string get_warning_icon_name(const TriangleMeshStats& stats) +{ + return stats.repaired() ? (stats.manifold() ? "exclamation_manifold" : "exclamation") : ""; +} + +std::pair ObjectList::get_mesh_errors(const int obj_idx, const int vol_idx /*= -1*/, bool from_plater /*= false*/) const { const int errors = get_mesh_errors_count(obj_idx, vol_idx); if (errors == 0) - return ""; // hide tooltip + return { "", "" }; // hide tooltip // Create tooltip string, if there are errors wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors) + ":\n"; @@ -407,21 +412,26 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx / if (stats.backwards_edges > 0) tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", stats.backwards_edges), stats.backwards_edges) + "\n"; - if (is_windows10()) - tooltip += _L("Right button click the icon to fix STL through Netfabb"); + if (!stats.manifold()) { + tooltip += _L("Remaning errors") + ":\n"; + tooltip += "\t" + format_wxstr(_L_PLURAL("%1$d open edge", "%1$d open edges", stats.open_edges), stats.open_edges) + "\n"; + } - return tooltip; + if (is_windows10() && !from_plater) + tooltip += "\n" + _L("Right button click the icon to fix STL through Netfabb"); + + return { tooltip, get_warning_icon_name(stats) }; } -wxString ObjectList::get_mesh_errors_list() +std::pair ObjectList::get_mesh_errors(bool from_plater /*= false*/) { if (!GetSelection()) - return ""; + return { "", "" }; int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx); - return get_mesh_errors_list(obj_idx, vol_idx); + return get_mesh_errors(obj_idx, vol_idx, from_plater); } void ObjectList::set_tooltip_for_item(const wxPoint& pt) @@ -459,7 +469,7 @@ void ObjectList::set_tooltip_for_item(const wxPoint& pt) { int obj_idx, vol_idx; get_selected_item_indexes(obj_idx, vol_idx, item); - tooltip = get_mesh_errors_list(obj_idx, vol_idx); + tooltip = get_mesh_errors(obj_idx, vol_idx).first; } GetMainWindow()->SetToolTip(tooltip); @@ -1775,8 +1785,12 @@ void ObjectList::del_subobject_item(wxDataViewItem& item) return; // If last volume item with warning was deleted, unmark object item - if (type & itVolume && (*m_objects)[obj_idx]->get_mesh_errors_count() == 0) - m_objects_model->DeleteWarningIcon(parent); + if (type & itVolume) { + if (auto obj = object(obj_idx); obj->get_mesh_errors_count() == 0) + m_objects_model->DeleteWarningIcon(parent); + else + m_objects_model->AddWarningIcon(parent, get_warning_icon_name(obj->mesh().stats())); + } m_objects_model->Delete(item); update_info_items(obj_idx); @@ -1985,7 +1999,7 @@ void ObjectList::split() for (const ModelVolume* volume : model_object->volumes) { const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(parent, from_u8(volume->name), volume->type(),// is_modifier() ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART, - volume->get_mesh_errors_count()>0, + get_warning_icon_name(volume->mesh().stats()), volume->config.has("extruder") ? volume->config.extruder() : 0, false); // add settings to the part, if it has those @@ -2484,7 +2498,7 @@ void ObjectList::part_selection_changed() if (item) { // wxGetApp().obj_manipul()->get_og()->set_value("object_name", m_objects_model->GetName(item)); wxGetApp().obj_manipul()->update_item_name(m_objects_model->GetName(item)); - wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors_list(obj_idx, volume_id)); + wxGetApp().obj_manipul()->update_warning_icon_state(get_mesh_errors(obj_idx, volume_id)); } } @@ -2627,7 +2641,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) const wxString& item_name = from_u8(model_object->name); const auto item = m_objects_model->Add(item_name, model_object->config.has("extruder") ? model_object->config.extruder() : 0, - get_mesh_errors_count(obj_idx) > 0); + get_warning_icon_name(model_object->mesh().stats())); update_info_items(obj_idx, nullptr, call_selection_changed); @@ -2637,7 +2651,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, from_u8(volume->name), volume->type(), - volume->get_mesh_errors_count()>0, + get_warning_icon_name(volume->mesh().stats()), volume->config.has("extruder") ? volume->config.extruder() : 0, false); add_settings_item(vol_item, &volume->config.get()); @@ -2746,6 +2760,8 @@ void ObjectList::delete_from_model_and_list(const std::vector& it // If last volume item with warning was deleted, unmark object item if (obj->get_mesh_errors_count() == 0) m_objects_model->DeleteWarningIcon(parent); + else + m_objects_model->AddWarningIcon(parent, get_warning_icon_name(obj->mesh().stats())); } wxGetApp().plater()->canvas3D()->ensure_on_bed(item->obj_idx, printer_technology() != ptSLA); } @@ -4162,8 +4178,10 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co // unmark fixed item only m_objects_model->DeleteWarningIcon(item); } - else - m_objects_model->AddWarningIcon(item); + else { + auto obj = object(obj_idx); + m_objects_model->AddWarningIcon(item, get_warning_icon_name(vol_idx < 0 ? obj->mesh().stats() : obj->volumes[vol_idx]->mesh().stats())); + } } void ObjectList::msw_rescale() @@ -4294,7 +4312,7 @@ wxDataViewItemArray ObjectList::reorder_volumes_and_get_selection(int obj_idx, s for (const ModelVolume* volume : object->volumes) { wxDataViewItem vol_item = m_objects_model->AddVolumeChild(object_item, from_u8(volume->name), volume->type(), - volume->get_mesh_errors_count() > 0, + get_warning_icon_name(volume->mesh().stats()), volume->config.has("extruder") ? volume->config.extruder() : 0, false); // add settings to the part, if it has those diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 0fbad19192..491bd26842 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -210,12 +210,12 @@ public: void get_selection_indexes(std::vector& obj_idxs, std::vector& vol_idxs); // Get count of errors in the mesh int get_mesh_errors_count(const int obj_idx, const int vol_idx = -1) const; - /* Get list of errors in the mesh. Return value is a string, used for the tooltip - * Function without parameters is for a call from Manipulation panel, - * when we don't know parameters of selected item - */ - wxString get_mesh_errors_list(const int obj_idx, const int vol_idx = -1) const; - wxString get_mesh_errors_list(); + // Get list of errors in the mesh and name of the warning icon + // Return value is a pair , used for the tooltip and related warning icon + // Function without parameters is for a call from Manipulation panel, + // when we don't know parameters of selected item + std::pair get_mesh_errors(const int obj_idx, const int vol_idx = -1, bool from_plater = false) const; + std::pair get_mesh_errors(bool from_plater = false); void set_tooltip_for_item(const wxPoint& pt); void selection_changed(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d35def1968..6eaa6316da 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -132,7 +132,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : return; wxGetApp().obj_list()->fix_through_netfabb(); - update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_list()); + update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors()); }); sizer->Add(m_fix_throught_netfab_bitmap); @@ -776,8 +776,12 @@ void ObjectManipulation::update_item_name(const wxString& item_name) m_item_name->SetLabel(item_name); } -void ObjectManipulation::update_warning_icon_state(const wxString& tooltip) -{ +void ObjectManipulation::update_warning_icon_state(const std::pair& warning) +{ + if (const std::string& warning_icon_name = warning.second; + !warning_icon_name.empty()) + m_manifold_warning_bmp = ScalableBitmap(m_parent, warning_icon_name); + const wxString& tooltip = warning.first; m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.bmp().GetSize()); m_fix_throught_netfab_bitmap->SetToolTip(tooltip); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index 92ba017757..9b77591be6 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -194,7 +194,7 @@ public: #endif // __APPLE__ void update_item_name(const wxString &item_name); - void update_warning_icon_state(const wxString& tooltip); + void update_warning_icon_state(const std::pair& warning); void msw_rescale(); void sys_color_changed(); void on_change(const std::string& opt_key, int axis, double new_value); diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 8ceb3cc231..746a99a2f3 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -37,6 +37,7 @@ void ObjectDataViewModelNode::init_container() static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char WarningIcon[] = "exclamation"; +static constexpr char WarningManifoldIcon[] = "exclamation_manifold"; struct InfoItemAtributes { std::string name; @@ -57,13 +58,15 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare Slic3r::ModelVolumeType type, const wxBitmap& bmp, const wxString& extruder, - const int idx/* = -1*/) : + const int idx/* = -1*/, + const std::string& warning_icon_name /*= std::string*/) : m_parent(parent), m_name(sub_obj_name), m_type(itVolume), m_volume_type(type), m_idx(idx), - m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : "") + m_extruder(type == Slic3r::ModelVolumeType::MODEL_PART || type == Slic3r::ModelVolumeType::PARAMETER_MODIFIER ? extruder : ""), + m_warning_icon_name(warning_icon_name) { m_bmp = bmp; set_action_and_extruder_icons(); @@ -170,6 +173,13 @@ void ObjectDataViewModelNode::set_printable_icon(PrintIndicator printable) create_scaled_bitmap(m_printable == piPrintable ? "eye_open.png" : "eye_closed.png"); } +void ObjectDataViewModelNode::set_warning_icon(const std::string& warning_icon_name) +{ + m_warning_icon_name = warning_icon_name; + if (warning_icon_name.empty()) + m_bmp = m_empty_bmp; +} + void ObjectDataViewModelNode::update_settings_digest_bitmaps() { m_bmp = m_empty_bmp; @@ -316,6 +326,8 @@ ObjectDataViewModel::ObjectDataViewModel() m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); + m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); + for (auto item : INFO_ITEMS) m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name); } @@ -328,15 +340,19 @@ ObjectDataViewModel::~ObjectDataViewModel() m_bitmap_cache = nullptr; } +wxBitmap& ObjectDataViewModel::GetWarningBitmap(const std::string& warning_icon_name) +{ + return warning_icon_name.empty() ? m_empty_bmp : warning_icon_name == WarningIcon ? m_warning_bmp : m_warning_manifold_bmp; +} + wxDataViewItem ObjectDataViewModel::Add(const wxString &name, const int extruder, - const bool has_errors/* = false*/) + const std::string& warning_icon_name/* = std::string()*/ ) { - const wxString extruder_str = extruder == 0 ? _(L("default")) : wxString::Format("%d", extruder); + const wxString extruder_str = extruder == 0 ? _L("default") : wxString::Format("%d", extruder); auto root = new ObjectDataViewModelNode(name, extruder_str); - // Add error icon if detected auto-repaire - if (has_errors) - root->m_bmp = m_warning_bmp; + // Add warning icon if detected auto-repaire + root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); m_objects.push_back(root); // notify control @@ -350,7 +366,7 @@ wxDataViewItem ObjectDataViewModel::Add(const wxString &name, wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, - const bool has_errors/* = false*/, + const std::string& warning_icon_name/* = std::string()*/, const int extruder/* = 0*/, const bool create_frst_child/* = true*/) { @@ -364,12 +380,10 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent if (insert_position < 0) insert_position = get_root_idx(root, itInstanceRoot); - const bool obj_errors = root->m_bmp.IsOk(); - if (create_frst_child && root->m_volumes_cnt == 0) { const Slic3r::ModelVolumeType type = Slic3r::ModelVolumeType::MODEL_PART; - const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, obj_errors), extruder_str, 0); + const auto node = new ObjectDataViewModelNode(root, root->m_name, type, GetVolumeIcon(type, root->m_warning_icon_name), extruder_str, 0, root->m_warning_icon_name); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // notify control @@ -380,12 +394,13 @@ wxDataViewItem ObjectDataViewModel::AddVolumeChild( const wxDataViewItem &parent if (insert_position >= 0) insert_position++; } - const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, has_errors), extruder_str, root->m_volumes_cnt); + const auto node = new ObjectDataViewModelNode(root, name, volume_type, GetVolumeIcon(volume_type, warning_icon_name), extruder_str, root->m_volumes_cnt, warning_icon_name); insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position); // if part with errors is added, but object wasn't marked, then mark it - if (!obj_errors && has_errors) - root->SetBitmap(m_warning_bmp); + if (!warning_icon_name.empty() && warning_icon_name != root->m_warning_icon_name && + (root->m_warning_icon_name.empty() || root->m_warning_icon_name == WarningManifoldIcon) ) + root->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); // notify control const wxDataViewItem child((void*)node); @@ -1665,6 +1680,7 @@ void ObjectDataViewModel::Rescale() { m_volume_bmps = MenuFactory::get_volume_bitmaps(); m_warning_bmp = create_scaled_bitmap(WarningIcon); + m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon); wxDataViewItemArray all_items; GetAllChildren(wxDataViewItem(0), all_items); @@ -1680,10 +1696,10 @@ void ObjectDataViewModel::Rescale() switch (node->m_type) { case itObject: - if (node->m_bmp.IsOk()) node->m_bmp = m_warning_bmp; + if (node->m_bmp.IsOk()) node->m_bmp = GetWarningBitmap(node->m_warning_icon_name); break; case itVolume: - node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_bmp.GetWidth() != node->m_bmp.GetHeight()); + node->m_bmp = GetVolumeIcon(node->m_volume_type, node->m_warning_icon_name); break; case itLayerRoot: node->m_bmp = create_scaled_bitmap(LayerRootIcon); @@ -1697,19 +1713,19 @@ void ObjectDataViewModel::Rescale() } } -wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const bool is_marked/* = false*/) +wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, const std::string& warning_icon_name/* = std::string()*/) { - if (!is_marked) + if (warning_icon_name.empty()) return m_volume_bmps[static_cast(vol_type)]; - std::string scaled_bitmap_name = "warning" + std::to_string(static_cast(vol_type)); - scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); + std::string scaled_bitmap_name = warning_icon_name + std::to_string(static_cast(vol_type)); + scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : "-lm"); wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); if (bmp == nullptr) { std::vector bmps; - bmps.emplace_back(m_warning_bmp); + bmps.emplace_back(GetWarningBitmap(warning_icon_name)); bmps.emplace_back(m_volume_bmps[static_cast(vol_type)]); bmp = m_bitmap_cache->insert(scaled_bitmap_name, bmps); @@ -1718,20 +1734,20 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty return *bmp; } -void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item) +void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item, const std::string& warning_icon_name) { if (!item.IsOk()) return; ObjectDataViewModelNode *node = static_cast(item.GetID()); if (node->GetType() & itObject) { - node->SetBitmap(m_warning_bmp); + node->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); return; } if (node->GetType() & itVolume) { - node->SetBitmap(GetVolumeIcon(node->GetVolumeType(), true)); - node->GetParent()->SetBitmap(m_warning_bmp); + node->SetWarningBitmap(GetVolumeIcon(node->GetVolumeType(), warning_icon_name), warning_icon_name); + node->GetParent()->SetWarningBitmap(GetWarningBitmap(warning_icon_name), warning_icon_name); return; } } @@ -1747,11 +1763,11 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo return; if (node->GetType() & itVolume) { - node->SetBitmap(m_volume_bmps[static_cast(node->volume_type())]); + node->SetWarningBitmap(m_volume_bmps[static_cast(node->volume_type())], ""); return; } - node->SetBitmap(wxNullBitmap); + node->SetWarningBitmap(wxNullBitmap, ""); if (unmark_object) { wxDataViewItemArray children; diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index f65f829f4b..95e53babdb 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -78,6 +78,7 @@ class ObjectDataViewModelNode wxBitmap m_action_icon; PrintIndicator m_printable {piUndef}; wxBitmap m_printable_icon; + std::string m_warning_icon_name{ "" }; std::string m_action_icon_name = ""; ModelVolumeType m_volume_type; @@ -100,7 +101,8 @@ public: Slic3r::ModelVolumeType type, const wxBitmap& bmp, const wxString& extruder, - const int idx = -1); + const int idx = -1, + const std::string& warning_icon_name = std::string()); ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const t_layer_height_range& layer_range, @@ -179,6 +181,7 @@ public: void SetVolumeType(ModelVolumeType type) { m_volume_type = type; } void SetBitmap(const wxBitmap &icon) { m_bmp = icon; } void SetExtruder(const wxString &extruder) { m_extruder = extruder; } + void SetWarningBitmap(const wxBitmap& icon, const std::string& warning_icon_name) { m_bmp = icon; m_warning_icon_name = warning_icon_name; } const wxBitmap& GetBitmap() const { return m_bmp; } const wxString& GetName() const { return m_name; } ItemType GetType() const { return m_type; } @@ -225,6 +228,8 @@ public: void set_extruder_icon(); // Set printable icon for node void set_printable_icon(PrintIndicator printable); + // Set warning icon for node + void set_warning_icon(const std::string& warning_icon); void update_settings_digest_bitmaps(); bool update_settings_digest(const std::vector& categories); @@ -253,7 +258,9 @@ class ObjectDataViewModel :public wxDataViewModel std::vector m_objects; std::vector m_volume_bmps; std::map m_info_bmps; + wxBitmap m_empty_bmp; wxBitmap m_warning_bmp; + wxBitmap m_warning_manifold_bmp; wxDataViewCtrl* m_ctrl { nullptr }; @@ -263,11 +270,11 @@ public: wxDataViewItem Add( const wxString &name, const int extruder, - const bool has_errors = false); + const std::string& warning_icon_name = std::string()); wxDataViewItem AddVolumeChild( const wxDataViewItem &parent_item, const wxString &name, const Slic3r::ModelVolumeType volume_type, - const bool has_errors = false, + const std::string& warning_icon_name = std::string(), const int extruder = 0, const bool create_frst_child = true); wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item); @@ -378,8 +385,8 @@ public: void Rescale(); wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, - const bool is_marked = false); - void AddWarningIcon(const wxDataViewItem& item); + const std::string& warning_icon_name = std::string()); + void AddWarningIcon(const wxDataViewItem& item, const std::string& warning_name); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; @@ -392,6 +399,8 @@ private: wxDataViewItem AddRoot(const wxDataViewItem& parent_item, const ItemType root_type); wxDataViewItem AddInstanceRoot(const wxDataViewItem& parent_item); void AddAllChildren(const wxDataViewItem& parent); + + wxBitmap& GetWarningBitmap(const std::string& warning_icon_name); }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 89b30a47fc..4ffa7dc134 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -125,6 +125,7 @@ wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); class ObjectInfo : public wxStaticBoxSizer { + std::string m_warning_icon_name{ "exclamation" }; public: ObjectInfo(wxWindow *parent); @@ -142,6 +143,7 @@ public: bool showing_manifold_warning_icon; void show_sizer(bool show); void msw_rescale(); + void update_warning_icon(const std::string& warning_icon_name); }; ObjectInfo::ObjectInfo(wxWindow *parent) : @@ -175,7 +177,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) : info_manifold_text->SetFont(wxGetApp().small_font()); info_manifold = new wxStaticText(parent, wxID_ANY, ""); info_manifold->SetFont(wxGetApp().small_font()); - manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap("exclamation")); + manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(m_warning_icon_name)); auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL); sizer_manifold->Add(info_manifold_text, 0); sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2); @@ -194,7 +196,15 @@ void ObjectInfo::show_sizer(bool show) void ObjectInfo::msw_rescale() { - manifold_warning_icon->SetBitmap(create_scaled_bitmap("exclamation")); + manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name)); +} + +void ObjectInfo::update_warning_icon(const std::string& warning_icon_name) +{ + if (warning_icon_name.empty()) + return; + m_warning_icon_name = warning_icon_name; + manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name)); } enum SlicedInfoIdx @@ -1129,30 +1139,19 @@ void Sidebar::show_info_sizer() p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef)); p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count()))); - const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats; + const auto& stats = model_object->get_object_stl_stats(); p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3))); p->object_info->info_facets->SetLabel(format_wxstr(_L_PLURAL("%1% (%2$d shell)", "%1% (%2$d shells)", stats.number_of_parts), static_cast(model_object->facets_count()), stats.number_of_parts)); - int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + - stats.facets_reversed + stats.backwards_edges; - if (errors > 0) { - wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors); - p->object_info->info_manifold->SetLabel(tooltip); + if (stats.repaired()) { + int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + stats.facets_reversed + stats.backwards_edges; + p->object_info->info_manifold->SetLabel(format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors)); - tooltip += ":\n"; - if (stats.degenerate_facets > 0) - tooltip += format_wxstr(_L_PLURAL("%1$d degenerate facet", "%1$d degenerate facets", stats.degenerate_facets), stats.degenerate_facets) + ", "; - if (stats.edges_fixed > 0) - tooltip += format_wxstr(_L_PLURAL("%1$d edge fixed", "%1$d edges fixed", stats.edges_fixed), stats.edges_fixed) + ", "; - if (stats.facets_removed > 0) - tooltip += format_wxstr(_L_PLURAL("%1$d facet removed", "%1$d facets removed", stats.facets_removed), stats.facets_removed) + ", "; - if (stats.facets_reversed > 0) - tooltip += format_wxstr(_L_PLURAL("%1$d facet reversed", "%1$d facets reversed", stats.facets_reversed), stats.facets_reversed) + ", "; - if (stats.backwards_edges > 0) - tooltip += format_wxstr(_L_PLURAL("%1$d backwards edge", "%1$d backwards edges", stats.backwards_edges), stats.backwards_edges) + ", "; - tooltip.RemoveLast(2);//remove last coma + auto mesh_errors = obj_list()->get_mesh_errors(true); + wxString tooltip = mesh_errors.first; + p->object_info->update_warning_icon(mesh_errors.second); p->object_info->showing_manifold_warning_icon = true; p->object_info->info_manifold->SetToolTip(tooltip); p->object_info->manifold_warning_icon->SetToolTip(tooltip); From 30d13c99b7c30cc5cde966bbdf7a869fad3b593e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 21 Sep 2021 22:41:55 +0200 Subject: [PATCH 11/25] Added a missing include (GCC 11.1). --- src/libslic3r/GCodeReader.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 657f47f0ec..7b106463a1 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include From 96b88f5b29f14d23f022d627b62e7c64883dcef8 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 22 Sep 2021 10:50:04 +0200 Subject: [PATCH 12/25] Fixed crash with zero support base spacing. Zero support base spacing is newly allowed, switching from the "support base" infill pattern to rectilinar infill pattern. Why someone would want to use a solid infill for support base eludes me, but it is simpler to support it instead of working out some rules on minimum support base density. Fixes Support patern spacing set to zero causing crash #6989 --- src/libslic3r/SupportMaterial.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index f727d334d9..2bcf99c57e 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -721,9 +721,9 @@ public: #ifdef SUPPORT_USE_AGG_RASTERIZER m_bbox = bbox; // Oversample the grid to avoid leaking of supports through or around the object walls. - int oversampling = std::min(8, int(scale_(m_support_spacing) / (scale_(params.extrusion_width) + 100))); - m_pixel_size = scale_(m_support_spacing / oversampling); - assert(scale_(params.extrusion_width) + 20 < m_pixel_size); + int extrusion_width_scaled = scale_(params.extrusion_width); + int oversampling = std::clamp(int(scale_(m_support_spacing) / (extrusion_width_scaled + 100)), 1, 8); + m_pixel_size = std::max(extrusion_width_scaled + 21, scale_(m_support_spacing / oversampling)); // Add one empty column / row boundaries. m_bbox.offset(m_pixel_size); // Grid size fitting the support polygons plus one pixel boundary around the polygons. @@ -2600,8 +2600,6 @@ void PrintObjectSupportMaterial::generate_base_layers( // No top contacts -> no intermediate layers will be produced. return; - // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); - BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_base_layers() in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, intermediate_layers.size()), @@ -2696,6 +2694,7 @@ void PrintObjectSupportMaterial::generate_base_layers( layer_intermediate.layer_type = sltBase; #if 0 + // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); // Fillet the base polygons and trim them again with the top, interface and contact layers. $base->{$i} = diff( offset2( @@ -3784,7 +3783,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; bool with_sheath = m_object_config->support_material_with_sheath; - InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipSupportBase); + InfillPattern infill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb : (support_density < 1.05 ? ipRectilinear : ipSupportBase); std::vector angles; angles.push_back(base_angle); From a6261ff4741dedbb992adb828ca6127767914d9a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 21 Sep 2021 09:31:15 +0200 Subject: [PATCH 13/25] Fix of #6898 (finishing slicing interfered with dragging gizmos) --- src/slic3r/GUI/Plater.cpp | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4ffa7dc134..e76ff24499 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3918,20 +3918,16 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) void Plater::priv::on_slicing_completed(wxCommandEvent & evt) { - switch (this->printer_technology) { - case ptFFF: - this->update_fff_scene(); - break; - case ptSLA: - if (view3D->is_dragging()) - delayed_scene_refresh = true; + if (view3D->is_dragging()) // updating scene now would interfere with the gizmo dragging + delayed_scene_refresh = true; + else { + if (this->printer_technology == ptFFF) + this->update_fff_scene(); else this->update_sla_scene(); - break; - default: break; } - } + void Plater::priv::on_export_began(wxCommandEvent& evt) { if (show_warning_dialog) @@ -4050,17 +4046,13 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) this->object_list_changed(); // refresh preview - switch (this->printer_technology) { - case ptFFF: - this->update_fff_scene(); - break; - case ptSLA: - if (view3D->is_dragging()) - delayed_scene_refresh = true; + if (view3D->is_dragging()) // updating scene now would interfere with the gizmo dragging + delayed_scene_refresh = true; + else { + if (this->printer_technology == ptFFF) + this->update_fff_scene(); else this->update_sla_scene(); - break; - default: break; } if (evt.cancelled()) { From 67e519d3ab8f56d9ab5d6759d1b92ee9131ead8c Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Wed, 22 Sep 2021 11:38:08 +0200 Subject: [PATCH 14/25] #6983 - Fixed preview when switching to it after automatic background processing is completed --- src/slic3r/GUI/Plater.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4ffa7dc134..dff4f197ef 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3750,10 +3750,10 @@ void Plater::priv::set_current_panel(wxPanel* panel) if (view3D->is_reload_delayed()) { // Delayed loading of the 3D scene. - if (this->printer_technology == ptSLA) { + if (printer_technology == ptSLA) { // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. - this->update_restart_background_process(true, false); + update_restart_background_process(true, false); } else view3D->reload_scene(true); } @@ -3776,8 +3776,14 @@ void Plater::priv::set_current_panel(wxPanel* panel) // FIXME: it may be better to have a single function making this check and let it be called wherever needed bool export_in_progress = this->background_process.is_export_scheduled(); bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; - if (!model.objects.empty() && !export_in_progress && model_fits) - this->q->reslice(); + if (!model.objects.empty() && !export_in_progress && model_fits) { +#if ENABLE_SEAMS_USING_MODELS + // the following call is needed to ensure that GCodeViewer buffers are initialized + // before calling reslice() when background processing is active + preview->SetFocusFromKbd(); +#endif // ENABLE_SEAMS_USING_MODELS + q->reslice(); + } // keeps current gcode preview, if any preview->reload_print(true); From d57d3f99d0874c09700e1a4b780caa14fe788d6e Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 22 Sep 2021 11:39:47 +0200 Subject: [PATCH 15/25] Fix of #3969 (crash if $USER is not set) --- src/slic3r/GUI/RemovableDriveManager.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index 593d8241d4..5fb8b01321 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -201,9 +201,7 @@ namespace search_for_drives_internal struct stat buf; stat(path.c_str(), &buf); uid_t uid = buf.st_uid; - std::string username(std::getenv("USER")); - struct passwd *pw = getpwuid(uid); - if (pw != 0 && pw->pw_name == username) + if (getuid() == uid) out.emplace_back(DriveData{ boost::filesystem::basename(boost::filesystem::path(path)), path }); } } @@ -245,7 +243,7 @@ std::vector RemovableDriveManager::search_for_removable_drives() cons search_for_drives_internal::search_path("/media/*", "/media", current_drives); //search_path("/Volumes/*", "/Volumes"); - std::string path(std::getenv("USER")); + std::string path = wxGetUserId().ToUTF8().data(); std::string pp(path); //search /media/USERNAME/* folder From 846b868215e1a35778c52ce8836346da977d592d Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 22 Sep 2021 12:39:19 +0200 Subject: [PATCH 16/25] Follow up ff82c82f - Fixed ModeButtons refreshing --- src/slic3r/GUI/wxExtensions.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index a3d6384e05..5e703a37b5 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -672,10 +672,9 @@ void ModeButton::focus_button(const bool focus) Slic3r::GUI::wxGetApp().normal_font(); SetFont(new_font); -//#ifdef _WIN32 -// GetParent()->Refresh(); -//#else -#ifndef _WIN32 +#ifdef _WIN32 + GetParent()->Refresh(); // force redraw a background of the selected mode button +#else SetForegroundColour(wxSystemSettings::GetColour(focus ? wxSYS_COLOUR_BTNTEXT : #if defined (__linux__) && defined (__WXGTK3__) wxSYS_COLOUR_GRAYTEXT From 8f064dd15511710641d08d0f3303494e90f0b352 Mon Sep 17 00:00:00 2001 From: Oleksandra Yushchenko Date: Wed, 22 Sep 2021 12:44:13 +0200 Subject: [PATCH 17/25] Check unsaved changes (#6991) * Check Unsaved changes (partially related to #5903) + Allow create new project when Plater is empty, but some of presets are modified (related to #5903) + When creating new project allow Keep or Discard modification from previous project + Added check of changes: * before any load project (including DnD and "Load From Recent Projects") * before preset updater * when configuration is changing from the ConfigWizard + Dialog caption is added for each check + Create/Destroy ConfigWizard every time when it's called * Check Unsaved changes: Next Improvements + For dialog "Save project changes" added a reason of saving and name of the current project (or "Untitled") + UnsavedChangesDialog: Headers are extended to better explain the reason + Preferences: Fixed tooltiops for "Always ask for unsaved changes when..." + Suppress "Remember my choice" checkbox for actions which are not frequently used * Fixed behavior of the application when try to save changed project but "Cancel" button is selected in "Save file as..." dialog * Check unsaved changes: Improvements for Config Wizard - Check all cases when presets should be updated + Fixed info line for Materials pages. Text of the info relates to the printer technology now * Improved suggested name for a project when Application is closing * Fixed Linux/OSX build warnings --- src/libslic3r/AppConfig.cpp | 3 + src/slic3r/GUI/ConfigWizard.cpp | 84 ++++++++++-- src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- src/slic3r/GUI/ExtraRenderers.cpp | 18 ++- src/slic3r/GUI/GUI_App.cpp | 174 ++++++++++++++++++------ src/slic3r/GUI/GUI_App.hpp | 7 +- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 26 ++-- src/slic3r/GUI/MainFrame.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 84 ++++++++---- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Preferences.cpp | 17 ++- src/slic3r/GUI/Tab.cpp | 20 ++- src/slic3r/GUI/Tab.hpp | 2 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 151 ++++++++++++-------- src/slic3r/GUI/UnsavedChangesDialog.hpp | 24 +++- src/slic3r/Utils/PresetUpdater.cpp | 22 +-- 17 files changed, 462 insertions(+), 178 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 177d8d708b..f3a1d59881 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -139,6 +139,9 @@ void AppConfig::set_defaults() if (get("default_action_on_select_preset").empty()) set("default_action_on_select_preset", "none"); // , "transfer", "discard" or "save" + if (get("default_action_on_new_project").empty()) + set("default_action_on_new_project", "none"); // , "keep(transfer)", "discard" or "save" + if (get("color_mapinulation_panel").empty()) set("color_mapinulation_panel", "0"); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 3f388f4850..ee6e3d5ab5 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -41,6 +41,7 @@ #include "format.hpp" #include "MsgDialog.hpp" #include "libslic3r/libslic3r.h" +#include "UnsavedChangesDialog.hpp" #if defined(__linux__) && defined(__WXGTK3__) #define wxLinux_gtk3 true @@ -741,10 +742,10 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector* are not compatible with some installed printers."); + wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); wxString text; if (all_printers) { - wxString second_line = _L("All installed printers are compatible with the selected filament."); + wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); text = wxString::Format( "" "