diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 8d2a79f2a..d0696aab0 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 9ec7451e5..1019ecf28 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 392d90707..c8649888f 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 53e340294..d8f1d3464 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 8ceb234be..2413bad5b 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 3490b8183..0da5e7380 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 e5491b1aa..8c3ab154a 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 33e2b9c94..8c30a6ae5 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 ddf377c78..26f5dc321 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 043f951ef..90fa9bfae 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 35b3e0cf4..9392485f6 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 cb7eb4549..54c373ce3 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 586fbafb5..000000000 --- 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 be5c5c61a..000000000 --- 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 932906fe0..2f2c9ec7f 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 d26e085e4..64c9ba428 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 a59165946..25250e234 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 7233ac40d..c9df648fb 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 2844f644c..8337ab384 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 07274d352..ea1d0ed17 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 55dae9430..9cac7f63b 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 20804193e..3ad7d62b1 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 5486741f2..196646dc9 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 61ff908d3..a09f5ea98 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 0cd80f20b..e11926c7e 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 4b377d9f1..adec5735a 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 fb3e73d04..23eb343d1 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 dd1aaf46e..94496bbf7 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 d289fca14..1e41c1be5 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 60ab975c4..d46fb4a8b 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 aa2763968..dd11420bb 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 7ea7ac3a9..5e08b58e7 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 c5dbdac9c..d63ce1f63 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 f693143c4..dfc10658d 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 298fb21b0..fb27aced5 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 da4a842d4..35e5bb83e 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 e4cbd77d4..c8142ba34 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 a2c4910e3..d4ee885b3 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 b22e72be9..cf4b18a86 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 6d5fbf11c..89b6a6bea 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 75232c930..576728b24 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 9dd08b6fe..126e88916 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 931d2f449..30c81f6f7 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 f5424dfd9..32e31c264 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 df237db96..6faaf1584 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 5ab000d04..0ebe47a07 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 c0792a943..3b834c442 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 1f5ca3845..ad4f8f296 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 a0b27f6d8..3553697ac 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 d26d6e2a2..db8c5e93e 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 b56909280..a69e09412 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 e160504de..f2cf7e834 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 be9bf9741..1082df200 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 d10a85b25..2264ad856 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 4013a1f83..453cc9218 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 35fbb48ee..34795681e 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 2b07c78ee..105fe0f05 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 54e686ae3..ca26750dc 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 2d364628e..f9e61c6a0 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};