mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-25 07:34:03 -06:00
Eradicated admesh from TriangleMesh:
TriangleMesh newly only holds indexed_triangle_set and TriangleMeshStats. TriangleMeshStats contains an excerpt of stl_stats. TriangleMeshStats are updated when initializing with indexed_triangle_set. Admesh triangle mesh fixing is newly only used when loading an STL. AMF / 3MF / OBJ file formats are already indexed triangle sets, thus they are no more converted to admesh stl_file format, nor fixed through admesh repair machinery. When importing AMF / 3MF / OBJ files, volume is calculated and if negative, all faces are flipped. Also a bounding box and number of open edges is calculated. Implemented its_number_of_patches(), its_num_open_edges() Optimized its_split(), its_is_splittable() using a visitor pattern. Reworked QHull integration into TriangleMesh: 1) Face normals were not right. 2) Indexed triangle set is newly emitted instead of duplicating vertices for each face. Fixed cut_mesh(): Orient the triangulated faces correctly.
This commit is contained in:
parent
f484953a5a
commit
8a2a9dba2f
59 changed files with 1056 additions and 1758 deletions
|
@ -305,8 +305,8 @@ namespace Slic3r {
|
|||
|
||||
struct Geometry
|
||||
{
|
||||
std::vector<float> vertices;
|
||||
std::vector<unsigned int> triangles;
|
||||
std::vector<Vec3f> vertices;
|
||||
std::vector<Vec3i> triangles;
|
||||
std::vector<std::string> custom_supports;
|
||||
std::vector<std::string> custom_seam;
|
||||
std::vector<std::string> 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<Vec3i> 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; i<triangles_count; ++i) {
|
||||
size_t index = src_start_id/3 + i;
|
||||
for (size_t i=0; i<triangles_count; ++i) {
|
||||
size_t index = volume_data.first_triangle_id + i;
|
||||
assert(index < geometry.custom_supports.size());
|
||||
assert(index < geometry.custom_seam.size());
|
||||
assert(index < geometry.mmu_segmentation.size());
|
||||
|
@ -2543,11 +2518,6 @@ namespace Slic3r {
|
|||
if (volume == nullptr)
|
||||
continue;
|
||||
|
||||
if (!volume->mesh().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;
|
||||
}
|
||||
|
|
|
@ -244,11 +244,11 @@ struct AMFParserContext
|
|||
// Map from obect name to object idx & instances.
|
||||
std::map<std::string, Object> m_object_instances_map;
|
||||
// Vertices parsed for the current m_object.
|
||||
std::vector<float> m_object_vertices;
|
||||
std::vector<Vec3f> 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<int> m_volume_facets;
|
||||
std::vector<Vec3i> 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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,333 +0,0 @@
|
|||
#include <string.h>
|
||||
#include <exception>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "miniz_extension.hpp"
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#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 <assert.h>
|
||||
|
||||
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<char> &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<char> &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<char> &scene_xml_data,
|
||||
// loaded data of this STL
|
||||
std::vector<char> &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<int, ModelObject*> &group_to_model_object)
|
||||
{
|
||||
// Find the model entry in the XML data.
|
||||
char model_name_tag[1024];
|
||||
sprintf(model_name_tag, "<model name=\"%s\">", name);
|
||||
const char *model_xml = strstr(scene_xml_data.data(), model_name_tag);
|
||||
const char *zero_tag = "<zero>";
|
||||
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 = "<position>";
|
||||
const char *position_xml = strstr(model_xml, position_tag);
|
||||
const char *rotation_tag = "<rotation>";
|
||||
const char *rotation_xml = strstr(model_xml, rotation_tag);
|
||||
const char *scale_tag = "<scale>";
|
||||
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 = "<group>";
|
||||
const char *group_xml = strstr(model_xml, group_tag);
|
||||
const char *extruder_tag = "<extruder>";
|
||||
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<stl_facet> 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<char> scene_xml_data;
|
||||
// For grouping multiple STLs into a single ModelObject for multi-material prints.
|
||||
std::map<int, ModelObject*> 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<char> 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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<float>();
|
||||
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<unsigned char>(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<float>(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<float>(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<float>().eval(), 0.f);
|
||||
m_extruder_colors[i] = static_cast<unsigned char>(i);
|
||||
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
|
||||
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
|
||||
}
|
||||
|
||||
if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
|
||||
|
|
|
@ -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<Vec3i> 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<float>());
|
||||
|
||||
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<stl_vertex> & 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<EpecKernel, EpicKernel> cvt;
|
||||
auto iv = cvt(v);
|
||||
return {iv.x(), iv.y(), iv.z()};
|
||||
return { float(iv.x()), float(iv.y()), float(iv.z()) };
|
||||
}
|
||||
|
||||
template<class _Mesh> TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh)
|
||||
{
|
||||
Pointf3s points;
|
||||
std::vector<Vec3i> 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<class _Mesh> 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<CGALMesh, CGALMeshDeleter>
|
||||
|
|
|
@ -28,65 +28,84 @@ template<> struct ItsWithNeighborsIndex_<indexed_triangle_set> {
|
|||
}
|
||||
};
|
||||
|
||||
// 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<class NeighborIndex>
|
||||
std::vector<size_t> its_find_unvisited_neighbors(
|
||||
const indexed_triangle_set &its,
|
||||
const NeighborIndex & neighbor_index,
|
||||
std::vector<char> & visited)
|
||||
{
|
||||
using stack_el = size_t;
|
||||
|
||||
auto facestack = reserve_vector<stack_el>(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<size_t> 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<typename Visitor>
|
||||
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<char> m_visited;
|
||||
|
||||
using stack_el = size_t;
|
||||
std::vector<stack_el> 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<class IndexT> 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_<Its>::get_its(m);
|
||||
|
||||
std::vector<char> visited(its.indices.size(), false);
|
||||
|
||||
struct VertexConv {
|
||||
size_t part_id = std::numeric_limits<size_t>::max();
|
||||
size_t vertex_image;
|
||||
};
|
||||
std::vector<VertexConv> vidx_conv(its.vertices.size());
|
||||
|
||||
const auto& neighbor_index = ItsWithNeighborsIndex_<Its>::get_index(m);
|
||||
|
||||
meshsplit_detail::NeighborVisitor visitor(its, meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_index(m));
|
||||
|
||||
std::vector<size_t> facets;
|
||||
for (size_t part_id = 0;; ++part_id) {
|
||||
std::vector<size_t> 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<indexed_triangle_set> its_split(const Its &its)
|
|||
return ret;
|
||||
}
|
||||
|
||||
template<class Its> bool its_is_splittable(const Its &m)
|
||||
template<class Its>
|
||||
bool its_is_splittable(const Its &m)
|
||||
{
|
||||
using namespace meshsplit_detail;
|
||||
const indexed_triangle_set &its = ItsWithNeighborsIndex_<Its>::get_its(m);
|
||||
const auto& neighbor_index = ItsWithNeighborsIndex_<Its>::get_index(m);
|
||||
meshsplit_detail::NeighborVisitor visitor(meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_its(m), meshsplit_detail::ItsWithNeighborsIndex_<Its>::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<char> 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<class Its>
|
||||
size_t its_number_of_patches(const Its &m)
|
||||
{
|
||||
meshsplit_detail::NeighborVisitor visitor(meshsplit_detail::ItsWithNeighborsIndex_<Its>::get_its(m), meshsplit_detail::ItsWithNeighborsIndex_<Its>::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<class ExPolicy>
|
||||
|
|
|
@ -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<TriangleMesh> 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<TriangleMesh>(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<TriangleMesh> 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<TriangleMesh*>(m_mesh.get())->scale(versor);
|
||||
const_cast<TriangleMesh*>(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;
|
||||
}
|
||||
|
|
|
@ -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<int> 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<const TriangleMesh>(mesh); }
|
||||
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
|
||||
void set_mesh(const indexed_triangle_set &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
|
||||
void set_mesh(indexed_triangle_set &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
|
||||
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
|
||||
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
|
||||
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
|
||||
|
@ -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!
|
||||
|
|
|
@ -286,8 +286,6 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
|||
|
||||
if (mesh.empty()) return;
|
||||
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
std::vector<ExPolygons> 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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -205,7 +205,6 @@ inline bool is_on_floor(const SLAPrintObjectConfig &cfg)
|
|||
std::vector<XYRotation> 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<double>(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<XYRotation>(chull.its.indices.size());
|
||||
auto rotcmp = [](const XYRotation &r1, const XYRotation &r2) {
|
||||
double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y];
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
})
|
||||
{}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ?
|
||||
|
|
|
@ -14,10 +14,8 @@ void simplify_mesh(indexed_triangle_set &);
|
|||
|
||||
template<class...Args> void simplify_mesh(TriangleMesh &m, Args &&...a)
|
||||
{
|
||||
m.require_shared_vertices();
|
||||
simplify_mesh(m.its, std::forward<Args>(a)...);
|
||||
m = TriangleMesh{m.its};
|
||||
m.require_shared_vertices();
|
||||
m = TriangleMesh{ std::move(m.its) };
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -15,25 +15,79 @@ namespace Slic3r {
|
|||
|
||||
class TriangleMesh;
|
||||
class TriangleMeshSlicer;
|
||||
typedef std::vector<TriangleMesh*> 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<Vec3i> &facets);
|
||||
TriangleMesh() = default;
|
||||
TriangleMesh(const std::vector<Vec3f> &vertices, const std::vector<Vec3i> &faces);
|
||||
TriangleMesh(std::vector<Vec3f> &&vertices, const std::vector<Vec3i> &&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<TriangleMesh> 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<double>(); }
|
||||
Vec3d size() const { return m_stats.size.cast<double>(); }
|
||||
/// 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<ExPolygons> slice(const std::vector<double>& 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<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &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<size_t>& triangles);
|
||||
|
||||
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its);
|
||||
std::vector<indexed_triangle_set> its_split(const indexed_triangle_set &its, std::vector<Vec3i> &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<Vec3i> &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<Vec3i> &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<Vec3i> &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<stl_triangle_vertex_indices> &indices, const std::vector<stl_vertex> &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<stl_triangle_vertex_indices> &indices, const std::vector<stl_vertex> &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 <class Archive> struct specialize<Archive, Slic3r::TriangleMesh, cereal::specialization::non_member_load_save> {};
|
||||
template<class Archive> 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<char*>(const_cast<Slic3r::TriangleMeshStats*>(&mesh.stats())), sizeof(Slic3r::TriangleMeshStats));
|
||||
archive(mesh.its.indices, mesh.its.vertices);
|
||||
}
|
||||
template<class Archive> 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<const char*>(&mesh.stats()), sizeof(Slic3r::TriangleMeshStats));
|
||||
archive(mesh.its.indices, mesh.its.vertices);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<void()> throw_on_cancel)
|
||||
{
|
||||
std::vector<Polygons> top, bottom;
|
||||
std::vector<float> 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<void()> throw_on_cancel)
|
||||
{
|
||||
std::vector<Polygons> top, bottom;
|
||||
std::vector<float> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -98,7 +98,21 @@ void slice_mesh_slabs(
|
|||
std::vector<Polygons> *out_bottom,
|
||||
std::function<void()> 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<void()> throw_on_cancel);
|
||||
|
||||
// Project mesh into 2D polygons.
|
||||
Polygons project_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
const Transform3d &trafo,
|
||||
std::function<void()> throw_on_cancel);
|
||||
|
||||
void cut_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
float z,
|
||||
indexed_triangle_set *upper,
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue