diff options
Diffstat (limited to 'src/libslic3r/Model.cpp')
-rw-r--r-- | src/libslic3r/Model.cpp | 416 |
1 files changed, 280 insertions, 136 deletions
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index b3a18f26b..c25026cc4 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,6 +1,9 @@ +#include "Exception.hpp" #include "Model.hpp" +#include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" +#include "TriangleSelector.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -19,7 +22,6 @@ #include "SVG.hpp" #include <Eigen/Dense> #include "GCodeWriter.hpp" -#include "GCode/PreviewData.hpp" namespace Slic3r { @@ -112,13 +114,13 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else - throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); if (! result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) o->input_file = input_file; @@ -142,13 +144,13 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig else if (boost::algorithm::iends_with(input_file, ".zip.amf")) result = load_amf(input_file.c_str(), config, &model, check_version); else - throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); if (!result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) { @@ -355,116 +357,6 @@ TriangleMesh Model::mesh() const return mesh; } -static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) -{ - if (sizes.empty()) - // return if the list is empty or the following call to BoundingBoxf constructor will lead to a crash - return true; - - // we supply unscaled data to arrange() - bool result = Slic3r::Geometry::arrange( - sizes.size(), // number of parts - BoundingBoxf(sizes).max, // width and height of a single cell - dist, // distance between cells - bb, // bounding box of the area to fill - out // output positions - ); - - if (!result && bb != nullptr) { - // Try to arrange again ignoring bb - result = Slic3r::Geometry::arrange( - sizes.size(), // number of parts - BoundingBoxf(sizes).max, // width and height of a single cell - dist, // distance between cells - nullptr, // bounding box of the area to fill - out // output positions - ); - } - - return result; -} - -/* arrange objects preserving their instance count - but altering their instance positions */ -bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) -{ - size_t count = 0; - for (auto obj : objects) count += obj->instances.size(); - - arrangement::ArrangePolygons input; - ModelInstancePtrs instances; - input.reserve(count); - instances.reserve(count); - for (ModelObject *mo : objects) - for (ModelInstance *minst : mo->instances) { - input.emplace_back(minst->get_arrange_polygon()); - instances.emplace_back(minst); - } - - arrangement::BedShapeHint bedhint; - coord_t bedwidth = 0; - - if (bb) { - bedwidth = scaled(bb->size().x()); - bedhint = arrangement::BedShapeHint( - BoundingBox(scaled(bb->min), scaled(bb->max))); - } - - arrangement::arrange(input, scaled(dist), bedhint); - - bool ret = true; - coord_t stride = bedwidth + bedwidth / 5; - - for(size_t i = 0; i < input.size(); ++i) { - if (input[i].bed_idx != 0) ret = false; - if (input[i].bed_idx >= 0) { - input[i].translation += Vec2crd{input[i].bed_idx * stride, 0}; - instances[i]->apply_arrange_result(input[i].translation, - input[i].rotation); - } - } - - return ret; -} - -// Duplicate the entire model preserving instance relative positions. -void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) -{ - Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size())); - Pointfs positions; - if (! _arrange(model_sizes, dist, bb, positions)) - throw std::invalid_argument("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); - - // note that this will leave the object count unaltered - - for (ModelObject *o : this->objects) { - // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = o->instances; - for (const ModelInstance *i : instances) { - for (const Vec2d &pos : positions) { - ModelInstance *instance = o->add_instance(*i); - instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0)); - } - } - o->invalidate_bounding_box(); - } -} - -/* this will append more instances to each object - and then automatically rearrange everything */ -void Model::duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) -{ - for (ModelObject *o : this->objects) { - // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = o->instances; - for (const ModelInstance *i : instances) - for (size_t k = 2; k <= copies_num; ++ k) - o->add_instance(*i); - } - - this->arrange_objects(dist, bb); -} - void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) { if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects"; @@ -557,6 +449,29 @@ void Model::convert_multipart_object(unsigned int max_extruders) this->objects.push_back(object); } +bool Model::looks_like_imperial_units() const +{ + if (this->objects.size() == 0) + return false; + + for (ModelObject* obj : this->objects) + if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3; + return true; + + return false; +} + +void Model::convert_from_imperial_units(bool only_small_volumes) +{ + double in_to_mm = 25.4; + for (ModelObject* obj : this->objects) + if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3; + obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); + for (ModelVolume* v : obj->volumes) + v->source.is_converted_from_inches = true; + } +} + void Model::adjust_min_z() { if (objects.empty()) @@ -693,6 +608,7 @@ void ModelObject::assign_new_unique_ids_recursive() model_volume->assign_new_unique_ids_recursive(); for (ModelInstance *model_instance : this->instances) model_instance->assign_new_unique_ids_recursive(); + this->layer_height_profile.set_new_unique_id(); } // Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original. @@ -862,6 +778,38 @@ TriangleMesh ModelObject::raw_mesh() const return mesh; } +// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. +// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater +// and to display the object statistics at ModelObject::print_info(). +indexed_triangle_set ModelObject::raw_indexed_triangle_set() const +{ + size_t num_vertices = 0; + size_t num_faces = 0; + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + num_vertices += v->mesh().its.vertices.size(); + num_faces += v->mesh().its.indices.size(); + } + indexed_triangle_set out; + out.vertices.reserve(num_vertices); + out.indices.reserve(num_faces); + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + size_t i = out.vertices.size(); + size_t j = out.indices.size(); + append(out.vertices, v->mesh().its.vertices); + append(out.indices, v->mesh().its.indices); + auto m = v->get_matrix(); + for (; i < out.vertices.size(); ++ i) + out.vertices[i] = (m * out.vertices[i].cast<double>()).cast<float>().eval(); + if (v->is_left_handed()) { + for (; j < out.indices.size(); ++ j) + std::swap(out.indices[j][0], out.indices[j][1]); + } + } + return out; +} + // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh ModelObject::full_raw_mesh() const { @@ -903,7 +851,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const m_raw_bounding_box_valid = true; m_raw_bounding_box.reset(); if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); for (const ModelVolume *v : this->volumes) @@ -1075,6 +1023,65 @@ void ModelObject::scale_mesh_after_creation(const Vec3d &versor) this->invalidate_bounding_box(); } +void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial, std::vector<int> volume_idxs) +{ + BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - start"; + + ModelObject* new_object = new_clone(*this); + + double koef = from_imperial ? 25.4 : 0.0393700787; + const Vec3d versor = Vec3d(koef, koef, koef); + + new_object->set_model(nullptr); + new_object->sla_support_points.clear(); + new_object->sla_drain_holes.clear(); + new_object->sla_points_status = sla::PointsStatus::NoPoints; + new_object->clear_volumes(); + new_object->input_file.clear(); + + int vol_idx = 0; + 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; + vol->set_type(volume->type()); + // Don't copy the config's ID. + vol->config.assign_config(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); + vol->set_material(volume->material_id(), *volume->material()); + vol->source.input_file = volume->source.input_file; + vol->source.object_idx = (int)new_objects.size(); + vol->source.volume_idx = vol_idx; + + vol->supported_facets.assign(volume->supported_facets); + vol->seam_facets.assign(volume->seam_facets); + + // Perform conversion only if the target "imperial" state is different from the current one. + // This check supports conversion of "mixed" set of volumes, each with different "imperial" state. + 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->source.is_converted_from_inches = from_imperial; + } + else + vol->set_offset(volume->get_offset()); + } + vol_idx ++; + } + new_object->invalidate_bounding_box(); + + new_objects.push_back(new_object); + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end"; +} + size_t ModelObject::materials_count() const { std::set<t_model_material_id> material_ids; @@ -1149,6 +1156,9 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b for (ModelVolume *volume : volumes) { const auto volume_matrix = volume->get_matrix(); + volume->supported_facets.clear(); + volume->seam_facets.clear(); + if (! volume->is_model_part()) { // Modifiers are not cut, but we still need to add the instance transformation // to the modifier volume transformation to preserve their shape properly. @@ -1192,7 +1202,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; // Don't copy the config's ID. - static_cast<DynamicPrintConfig&>(vol->config) = static_cast<const DynamicPrintConfig&>(volume->config); + vol->config.assign_config(volume->config); assert(vol->config.id().valid()); assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); @@ -1201,8 +1211,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; // Don't copy the config's ID. - static_cast<DynamicPrintConfig&>(vol->config) = static_cast<const DynamicPrintConfig&>(volume->config); - assert(vol->config.id().valid()); + vol->config.assign_config(volume->config); + assert(vol->config.id().valid()); assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); @@ -1273,13 +1283,17 @@ void ModelObject::split(ModelObjectPtrs* new_objects) ModelVolume* volume = this->volumes.front(); TriangleMeshPtrs meshptrs = volume->mesh().split(); for (TriangleMesh *mesh : meshptrs) { + + // FIXME: crashes if not satisfied + if (mesh->facets_count() < 3) continue; + mesh->repair(); // 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(); new_object->name = this->name; // Don't copy the config's ID. - static_cast<DynamicPrintConfig&>(new_object->config) = static_cast<const DynamicPrintConfig&>(this->config); + new_object->config.assign_config(this->config); assert(new_object->config.id().valid()); assert(new_object->config.id() != this->config.id()); new_object->instances.reserve(this->instances.size()); @@ -1303,6 +1317,27 @@ void ModelObject::split(ModelObjectPtrs* new_objects) return; } +void ModelObject::merge() +{ + if (this->volumes.size() == 1) { + // We can't merge meshes if there's just one volume + return; + } + + TriangleMesh mesh; + + for (ModelVolume* volume : volumes) + if (!volume->mesh().empty()) + mesh.merge(volume->mesh()); + mesh.repair(); + + this->clear_volumes(); + ModelVolume* vol = this->add_volume(mesh); + + if (!vol) + return; +} + // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // This situation is solved by baking in the instance transformation into the mesh vertices. @@ -1415,8 +1450,8 @@ unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3 inside_outside |= OUTSIDE; } model_instance->print_volume_state = - (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstance::PVS_Partly_Outside : - (inside_outside == INSIDE) ? ModelInstance::PVS_Inside : ModelInstance::PVS_Fully_Outside; + (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstancePVS_Partly_Outside : + (inside_outside == INSIDE) ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside; if (inside_outside == INSIDE) ++ num_printable; } @@ -1496,9 +1531,6 @@ stl_stats ModelObject::get_object_stl_stats() const // fill full_stats from all objet's meshes for (ModelVolume* volume : this->volumes) { - if (volume->id() == this->volumes[0]->id()) - continue; - const stl_stats& stats = volume->mesh().stl.stats; // initialize full_stats (for repaired errors) @@ -1708,6 +1740,14 @@ void ModelObject::scale_to_fit(const Vec3d &size) */ } +void ModelVolume::assign_new_unique_ids_recursive() +{ + ObjectBase::set_new_unique_id(); + config.set_new_unique_id(); + supported_facets.set_new_unique_id(); + seam_facets.set_new_unique_id(); +} + void ModelVolume::rotate(double angle, Axis axis) { switch (axis) @@ -1768,6 +1808,14 @@ void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_hand this->set_new_unique_id(); } +void ModelVolume::convert_from_imperial_units() +{ + double in_to_mm = 25.4; + this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); + this->set_offset(Vec3d(0, 0, 0)); + this->source.is_converted_from_inches = true; +} + void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->transform(get_matrix(dont_translate)); @@ -1820,7 +1868,7 @@ void ModelInstance::transform_polygon(Polygon* polygon) const arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const { - static const double SIMPLIFY_TOLERANCE_MM = 0.1; +// static const double SIMPLIFY_TOLERANCE_MM = 0.1; Vec3d rotation = get_rotation(); rotation.z() = 0.; @@ -1832,13 +1880,11 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const assert(!p.points.empty()); - // this may happen for malformed models, see: - // https://github.com/prusa3d/PrusaSlicer/issues/2209 - if (!p.points.empty()) { - Polygons pp{p}; - pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM)); - if (!pp.empty()) p = pp.front(); - } +// if (!p.points.empty()) { +// Polygons pp{p}; +// pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM)); +// if (!pp.empty()) p = pp.front(); +// } arrangement::ArrangePolygon ret; ret.poly.contour = std::move(p); @@ -1848,6 +1894,83 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const return ret; } +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const +{ + TriangleSelector selector(mv.mesh()); + selector.deserialize(m_data); + indexed_triangle_set out = selector.get_facets(type); + return out; +} + +bool FacetsAnnotation::set(const TriangleSelector& selector) +{ + std::map<int, std::vector<bool>> sel_map = selector.serialize(); + if (sel_map != m_data) { + m_data = sel_map; + this->touch(); + return true; + } + return false; +} + +void FacetsAnnotation::clear() +{ + m_data.clear(); + this->reset_timestamp(); +} + +// Following function takes data from a triangle and encodes it as string +// of hexadecimal numbers (one digit per triangle). Used for 3MF export, +// changing it may break backwards compatibility !!!!! +std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const +{ + std::string out; + + auto triangle_it = m_data.find(triangle_idx); + if (triangle_it != m_data.end()) { + const std::vector<bool>& code = triangle_it->second; + int offset = 0; + while (offset < int(code.size())) { + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[offset + i]); + } + offset += 4; + + assert(next_code >=0 && next_code <= 15); + char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A'; + out.insert(out.begin(), digit); + } + } + return out; +} + +// Recover triangle splitting & state from string of hexadecimal values previously +// generated by get_triangle_as_string. Used to load from 3MF. +void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) +{ + assert(! str.empty()); + m_data[triangle_id] = std::vector<bool>(); // zero current state or create new + std::vector<bool>& code = m_data[triangle_id]; + + for (auto it = str.crbegin(); it != str.crend(); ++it) { + const char ch = *it; + int dec = 0; + if (ch >= '0' && ch<='9') + dec = int(ch - '0'); + else if (ch >='A' && ch <= 'F') + dec = 10 + int(ch - 'A'); + else + assert(false); + + // Convert to binary and append into code. + for (int i=0; i<4; ++i) { + code.insert(code.end(), bool(dec & (1 << i))); + } + } +} + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) @@ -1911,6 +2034,26 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO return false; } +bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new) { + assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART)); + assert(mo.volumes.size() == mo_new.volumes.size()); + for (size_t i=0; i<mo.volumes.size(); ++i) { + if (! mo_new.volumes[i]->supported_facets.timestamp_matches(mo.volumes[i]->supported_facets)) + return true; + } + return false; +} + +bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new) { + assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART)); + assert(mo.volumes.size() == mo_new.volumes.size()); + for (size_t i=0; i<mo.volumes.size(); ++i) { + if (! mo_new.volumes[i]->seam_facets.timestamp_matches(mo.volumes[i]->seam_facets)) + return true; + } + return false; +} + extern bool model_has_multi_part_objects(const Model &model) { for (const ModelObject *model_object : model.objects) @@ -1921,7 +2064,7 @@ extern bool model_has_multi_part_objects(const Model &model) extern bool model_has_advanced_features(const Model &model) { - auto config_is_advanced = [](const DynamicPrintConfig &config) { + auto config_is_advanced = [](const ModelConfig &config) { return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder")); }; for (const ModelObject *model_object : model.objects) { @@ -1991,6 +2134,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2) } } } + #endif /* NDEBUG */ } |