diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 715371521a..7f3fbf2ede 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -51,6 +51,8 @@ set(lisbslic3r_sources Config.hpp CurveAnalyzer.cpp CurveAnalyzer.hpp + CutUtils.cpp + CutUtils.hpp EdgeGrid.cpp EdgeGrid.hpp ElephantFootCompensation.cpp diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp new file mode 100644 index 0000000000..5dc5b82fa3 --- /dev/null +++ b/src/libslic3r/CutUtils.cpp @@ -0,0 +1,652 @@ +#include "CutUtils.hpp" +#include "Geometry.hpp" +#include "libslic3r.h" +#include "Model.hpp" +#include "TriangleMeshSlicer.hpp" +#include "TriangleSelector.hpp" +#include "ObjectID.hpp" +#include + +namespace Slic3r { + +using namespace Geometry; + +static void apply_tolerance(ModelVolume *vol) +{ + ModelVolume::CutInfo &cut_info = vol->cut_info; + + assert(cut_info.is_connector); + if (!cut_info.is_processed) return; + + Vec3d sf = vol->get_scaling_factor(); + + // make a "hole" wider + sf[X] += double(cut_info.radius_tolerance); + sf[Y] += double(cut_info.radius_tolerance); + + // make a "hole" dipper + sf[Z] += double(cut_info.height_tolerance); + + vol->set_scaling_factor(sf); + + // correct offset in respect to the new depth + Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ(); + if (rot_norm.norm() != 0.0) rot_norm.normalize(); + + double z_offset = 0.5 * static_cast(cut_info.height_tolerance); + if (cut_info.connector_type == CutConnectorType::Plug || cut_info.connector_type == CutConnectorType::Snap) z_offset -= 0.05; // add small Z offset to better preview + + vol->set_offset(vol->get_offset() + rot_norm * z_offset); +} + +static void add_cut_volume(TriangleMesh & mesh, + ModelObject * object, + const ModelVolume *src_volume, + const Transform3d &cut_matrix, + const std::string &suffix = {}, + ModelVolumeType type = ModelVolumeType::MODEL_PART) +{ + if (mesh.empty()) + return; + + mesh.transform(cut_matrix); + ModelVolume *vol = object->add_volume(mesh); + vol->set_type(type); + + vol->name = src_volume->name + suffix; + // Don't copy the config's ID. + vol->config.assign_config(src_volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != src_volume->config.id()); + vol->set_material(src_volume->material_id(), *src_volume->material()); + vol->cut_info = src_volume->cut_info; +} + +static void process_volume_cut(ModelVolume * volume, + const Transform3d & instance_matrix, + const Transform3d & cut_matrix, + ModelObjectCutAttributes attributes, + TriangleMesh & upper_mesh, + TriangleMesh & lower_mesh) +{ + const auto volume_matrix = volume->get_matrix(); + + const Transformation cut_transformation = Transformation(cut_matrix); + + const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset()); + + // Transform the mesh by the combined transformation matrix. + // Flip the triangles in case the composite transformation is left handed. + TriangleMesh mesh(volume->mesh()); + mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true); + + indexed_triangle_set upper_its, lower_its; + cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its); + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper_mesh = TriangleMesh(upper_its); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower_mesh = TriangleMesh(lower_its); +} + +static void process_connector_cut(ModelVolume * volume, + const Transform3d & instance_matrix, + const Transform3d & cut_matrix, + ModelObjectCutAttributes attributes, + ModelObject * upper, + ModelObject * lower, + std::vector &dowels) +{ + assert(volume->cut_info.is_connector); + volume->cut_info.set_processed(); + + const auto volume_matrix = volume->get_matrix(); + + // ! Don't apply instance transformation for the conntectors. + // This transformation is already there + if (volume->cut_info.connector_type != CutConnectorType::Dowel) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + ModelVolume *vol = nullptr; + if (volume->cut_info.connector_type == CutConnectorType::Snap) { + TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.)); + + vol = upper->add_volume(std::move(mesh)); + vol->set_transformation(volume->get_transformation()); + vol->set_type(ModelVolumeType::NEGATIVE_VOLUME); + + vol->cut_info = volume->cut_info; + vol->name = volume->name; + } else + vol = upper->add_volume(*volume); + + vol->set_transformation(volume_matrix); + apply_tolerance(vol); + } + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { + ModelVolume *vol = lower->add_volume(*volume); + vol->set_transformation(volume_matrix); + // for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug + vol->set_type(ModelVolumeType::MODEL_PART); + } + } else { + if (attributes.has(ModelObjectCutAttribute::CreateDowels)) { + ModelObject *dowel{nullptr}; + // Clone the object to duplicate instances, materials etc. + volume->get_object()->clone_for_cut(&dowel); + + // add one more solid part same as connector if this connector is a dowel + ModelVolume *vol = dowel->add_volume(*volume); + vol->set_type(ModelVolumeType::MODEL_PART); + + // But discard rotation and Z-offset for this volume + vol->set_rotation(Vec3d::Zero()); + vol->set_offset(Z, 0.0); + + dowels.push_back(dowel); + } + + // Cut the dowel + apply_tolerance(volume); + + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh); + + // add small Z offset to better preview + upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast()); + lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast()); + + // Add cut parts to the related objects + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type()); + add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type()); + } +} + +static void process_modifier_cut(ModelVolume *volume, const Transform3d &instance_matrix, const Transform3d &inverse_cut_matrix, ModelObjectCutAttributes attributes, ModelObject *upper, ModelObject *lower) +{ + const auto volume_matrix = instance_matrix * volume->get_matrix(); + + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + volume->set_transformation(Transformation(volume_matrix)); + + if (attributes.has(ModelObjectCutAttribute::CutToParts)) { + upper->add_volume(*volume); + return; + } + + // Some logic for the negative volumes/connectors. Add only needed modifiers + auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix); + bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0; + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut)) + lower->add_volume(*volume); +} + +static void process_solid_part_cut( + ModelVolume *volume, const Transform3d &instance_matrix, const Transform3d &cut_matrix, ModelObjectCutAttributes attributes, ModelObject *upper, ModelObject *lower) +{ + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh); + + // Add required cut parts to the objects + + if (attributes.has(ModelObjectCutAttribute::CutToParts)) { + add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A"); + if (!lower_mesh.empty()) { + add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B"); + upper->volumes.back()->cut_info.is_from_upper = false; + } + return; + } + + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { + add_cut_volume(upper_mesh, upper, volume, cut_matrix); + } + + if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) { + add_cut_volume(lower_mesh, lower, volume, cut_matrix); + } +} + +static void reset_instance_transformation(ModelObject * object, + size_t src_instance_idx, + const Transform3d &cut_matrix = Transform3d::Identity(), + bool place_on_cut = false, + bool flip = false, + bool is_set_offset = false, + bool offset_pos_dir = true) +{ + // Reset instance transformation except offset and Z-rotation + + for (size_t i = 0; i < object->instances.size(); ++i) { + auto & obj_instance = object->instances[i]; + const double rot_z = obj_instance->get_rotation().z(); + + Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()); + // add respect to mirroring + if (obj_instance->is_left_handed()) + inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1))); + + obj_instance->set_transformation(inst_trafo); + + if (is_set_offset && object->volumes.size() > 0) { + BoundingBoxf3 curBox; + for (size_t i = 0; i < object->volumes.size(); i++) { + curBox.merge(object->volumes[i]->mesh().bounding_box()); + } + auto offset_x = curBox.size().x() * 0.7 * (offset_pos_dir ? 1 : -1); + Vec3d displace(offset_x,0,0); + displace = rotation_transform(obj_instance->get_rotation()) * displace; + obj_instance->set_offset(obj_instance->get_offset() + displace); + } + + Vec3d rotation = Vec3d::Zero(); + if (!flip && !place_on_cut) { + if (i != src_instance_idx) + rotation[Z] = rot_z; + } else { + Transform3d rotation_matrix = Transform3d::Identity(); + if (flip) + rotation_matrix = rotation_transform(PI * Vec3d::UnitX()); + + if (place_on_cut) + rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse(); + + if (i != src_instance_idx) + rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix; + + rotation = Transformation(rotation_matrix).get_rotation(); + } + + obj_instance->set_rotation(rotation); + } +} + +Cut::Cut(const ModelObject * object, + int instance, + const Transform3d & cut_matrix, + ModelObjectCutAttributes attributes /*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::CutToParts*/) + : m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes) +{ + m_model = Model(); + if (object) m_model.add_object(*object); +} + +void Cut::post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &cut_object_ptrs, bool keep, bool place_on_cut, bool flip) +{ + if (!object) return; + + if (keep && !object->volumes.empty()) { + reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip,set_offset_for_two_part, is_upper); + cut_object_ptrs.push_back(object); + } else + m_model.objects.push_back(object); // will be deleted in m_model.clear_objects(); +} + +void Cut::post_process(ModelObject *upper, ModelObject *lower, ModelObjectPtrs &cut_object_ptrs) +{ + post_process(upper,true, cut_object_ptrs, m_attributes.has(ModelObjectCutAttribute::KeepUpper), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), + m_attributes.has(ModelObjectCutAttribute::FlipUpper)); + + post_process(lower, false, cut_object_ptrs, m_attributes.has(ModelObjectCutAttribute::KeepLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), + m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower)); +} + +void Cut::finalize(const ModelObjectPtrs &objects) +{ + // clear model from temporarry objects + m_model.clear_objects(); + + // add to model result objects + m_model.objects = objects; +} + +const ModelObjectPtrs &Cut::perform_with_plane() +{ + if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) { + m_model.clear_objects(); + return m_model.objects; + } + + ModelObject *mo = m_model.objects.front(); + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; + + // Clone the object to duplicate instances, materials etc. + ModelObject *upper{nullptr}; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) + mo->clone_for_cut(&upper); + + ModelObject *lower{nullptr}; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::CutToParts)) + mo->clone_for_cut(&lower); + + std::vector dowels; + + // Because transformations are going to be applied to meshes directly, + // we reset transformation of all instances and volumes, + // except for translation and Z-rotation on instances, which are preserved + // in the transformation matrix and not applied to the mesh transform. + + const auto instance_matrix = mo->instances[m_instance]->get_transformation().get_matrix_no_offset(); + const Transformation cut_transformation = Transformation(m_cut_matrix); + const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset()); + + for (ModelVolume *volume : mo->volumes) { + volume->reset_extra_facets(); + + if (!volume->is_model_part()) { + if (volume->cut_info.is_processed){ + process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower); + } + else{ + process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels); + } + } else if (!volume->mesh().empty()) { + process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower); + } + } + + // Post-process cut parts + if (m_attributes.has(ModelObjectCutAttribute::CutToParts) && upper->volumes.empty()) { + m_model = Model(); + m_model.objects.push_back(upper); + return m_model.objects; + } + + ModelObjectPtrs cut_object_ptrs; + + if (m_attributes.has(ModelObjectCutAttribute::CutToParts) && !upper->volumes.empty()) { + reset_instance_transformation(upper, m_instance, m_cut_matrix); + cut_object_ptrs.push_back(upper); + } else { + // Delete all modifiers which are not intersecting with solid parts bounding box + auto delete_extra_modifiers = [this](ModelObject *mo) { + if (!mo) return; + const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance); + const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix(); + + for (int i = int(mo->volumes.size()) - 1; i >= 0; --i) + if (const ModelVolume *vol = mo->volumes[i]; !vol->is_model_part() && !vol->is_cut_connector()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + if (!obj_bb.intersects(bb)) + mo->delete_volume(i); + } + }; + + post_process(upper, lower, cut_object_ptrs); + delete_extra_modifiers(upper); + delete_extra_modifiers(lower); + + if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) { + for (auto dowel : dowels) { + reset_instance_transformation(dowel, m_instance); + dowel->name += "-Dowel-" + dowel->volumes[0]->name; + cut_object_ptrs.push_back(dowel); + } + } + } + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end"; + + finalize(cut_object_ptrs); + + return m_model.objects; +} + +static void distribute_modifiers_from_object(ModelObject *from_obj, const int instance_idx, ModelObject *to_obj1, ModelObject *to_obj2) +{ + auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3(); + auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3(); + const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix(); + + for (ModelVolume *vol : from_obj->volumes) + if (!vol->is_model_part()) { + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); + // Don't add modifiers which are not intersecting with solid parts + if (obj1_bb.intersects(bb)) to_obj1->add_volume(*vol); + if (obj2_bb.intersects(bb)) to_obj2->add_volume(*vol); + } +} + +static void merge_solid_parts_inside_object(ModelObjectPtrs &objects) +{ + for (ModelObject *mo : objects) { + TriangleMesh mesh; + // Merge all SolidPart but not Connectors + for (const ModelVolume *mv : mo->volumes) { + if (mv->is_model_part() && !mv->is_cut_connector()) { + TriangleMesh m = mv->mesh(); + m.transform(mv->get_matrix()); + mesh.merge(m); + } + } + if (!mesh.empty()) { + ModelVolume *new_volume = mo->add_volume(mesh); + new_volume->name = mo->name; + // Delete all merged SolidPart but not Connectors + for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) { + const ModelVolume *mv = mo->volumes[i]; + if (mv->is_model_part() && !mv->is_cut_connector()) mo->delete_volume(i); + } + } + } +} + +const ModelObjectPtrs &Cut::perform_by_contour(std::vector parts, int dowels_count) +{ + ModelObject *cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject *upper{nullptr}; + if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) + cut_mo->clone_for_cut(&upper); + ModelObject *lower{nullptr}; + if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) + cut_mo->clone_for_cut(&lower); + + const size_t cut_parts_cnt = parts.size(); + bool has_modifiers = false; + + // Distribute SolidParts to the Upper/Lower object + for (size_t id = 0; id < cut_parts_cnt; ++id) { + if (parts[id].is_modifier) + has_modifiers = true; // modifiers will be added later to the related parts + else if (ModelObject *obj = (parts[id].selected ? upper : lower)) + obj->add_volume(*(cut_mo->volumes[id])); + } + + if (has_modifiers) { + // Distribute Modifiers to the Upper/Lower object + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + } + + ModelObjectPtrs cut_object_ptrs; + + ModelVolumePtrs &volumes = cut_mo->volumes; + if (volumes.size() == cut_parts_cnt) { + // Means that object is cut without connectors + + // Just add Upper and Lower objects to cut_object_ptrs + post_process(upper, lower, cut_object_ptrs); + } else if (volumes.size() > cut_parts_cnt) { + // Means that object is cut with connectors + + // All volumes are distributed to Upper / Lower object, + // So we don’t need them anymore + for (size_t id = 0; id < cut_parts_cnt; id++) delete *(volumes.begin() + id); + volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt); + + // Perform cut just to get connectors + Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes); + const ModelObjectPtrs &cut_connectors_obj = cut.perform_with_plane(); + assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2); + + // Connectors from upper object + for (const ModelVolume *volume : cut_connectors_obj[0]->volumes) upper->add_volume(*volume, volume->type()); + + // Connectors from lower object + for (const ModelVolume *volume : cut_connectors_obj[1]->volumes) lower->add_volume(*volume, volume->type()); + + // Add Upper and Lower objects to cut_object_ptrs + post_process(upper, lower, cut_object_ptrs); + + // Add Dowel-connectors as separate objects to cut_object_ptrs + if (cut_connectors_obj.size() >= 3) + for (size_t id = 2; id < cut_connectors_obj.size(); id++) cut_object_ptrs.push_back(cut_connectors_obj[id]); + } + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + + finalize(cut_object_ptrs); + + return m_model.objects; +} + +const ModelObjectPtrs &Cut::perform_with_groove(const Groove &groove, const Transform3d &rotation_m, bool keep_as_parts /* = false*/) +{ + ModelObject *cut_mo = m_model.objects.front(); + + // Clone the object to duplicate instances, materials etc. + ModelObject *upper{nullptr}; + cut_mo->clone_for_cut(&upper); + ModelObject *lower{nullptr}; + cut_mo->clone_for_cut(&lower); + + const double groove_half_depth = 0.5 * double(groove.depth); + + Model tmp_model_for_cut = Model(); + + Model tmp_model = Model(); + tmp_model.add_object(*cut_mo); + ModelObject *tmp_object = tmp_model.objects.front(); + + auto add_volumes_from_cut = [](ModelObject *object, const ModelObjectCutAttribute attribute, const Model &tmp_model_for_cut) { + const auto &volumes = tmp_model_for_cut.objects.front()->volumes; + for (const ModelVolume *volume : volumes) + if (volume->is_model_part()) { + if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) || + (attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) { + ModelVolume *new_vol = object->add_volume(*volume); + new_vol->reset_from_upper(); + } + } + }; + + auto cut = [this, add_volumes_from_cut](ModelObject *object, const Transform3d &cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model &tmp_model_for_cut) { + Cut cut(object, m_instance, cut_matrix); + + tmp_model_for_cut = Model(); + tmp_model_for_cut.add_object(*cut.perform_with_plane().front()); + assert(!tmp_model_for_cut.objects.empty()); + + object->clear_volumes(); + add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut); + reset_instance_transformation(object, m_instance); + }; + + // cut by upper plane + + const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by lower plane + + const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix; + { + cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + } + + // cut middle part with 2 angles and add parts to related upper/lower objects + + const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle)); + + // cut by angle1 plane + { + const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * + rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + + cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // cut by angle2 plane + { + const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * + rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + + cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // apply tolerance to the middle part + { + const double h_groove_shift_tolerance = groove_half_depth - (double) groove.depth_tolerance; + + const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix; + cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + + const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance); + + const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * + rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle)); + cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * + rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle)); + cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut); + } + + // this part can be added to the upper object now + add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut); + + ModelObjectPtrs cut_object_ptrs; + + if (keep_as_parts) { + // add volumes from lower object to the upper, but mark them as a lower + const auto &volumes = lower->volumes; + for (const ModelVolume *volume : volumes) { + ModelVolume *new_vol = upper->add_volume(*volume); + new_vol->cut_info.is_from_upper = false; + } + + // add modifiers + for (const ModelVolume *volume : cut_mo->volumes) + if (!volume->is_model_part()) upper->add_volume(*volume); + + cut_object_ptrs.push_back(upper); + + // add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks + cut_object_ptrs.push_back(lower); + } else { + // add modifiers if object has any + for (const ModelVolume *volume : cut_mo->volumes) + if (!volume->is_model_part()) { + distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); + break; + } + + assert(!upper->volumes.empty() && !lower->volumes.empty()); + + // Add Upper and Lower parts to cut_object_ptrs + + post_process(upper, lower, cut_object_ptrs); + + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); + } + + finalize(cut_object_ptrs); + + return m_model.objects; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/CutUtils.hpp b/src/libslic3r/CutUtils.hpp new file mode 100644 index 0000000000..a3b5159737 --- /dev/null +++ b/src/libslic3r/CutUtils.hpp @@ -0,0 +1,58 @@ +#ifndef slic3r_CutUtils_hpp_ +#define slic3r_CutUtils_hpp_ + +#include "enum_bitmask.hpp" +#include "Point.hpp" +#include "Model.hpp" + +namespace Slic3r { + + +struct Groove +{ + float depth{0.f}; + float width{0.f}; + float flaps_angle{0.f}; + float angle{0.f}; + float depth_init{0.f}; + float width_init{0.f}; + float flaps_angle_init{0.f}; + float angle_init{0.f}; + float depth_tolerance{0.1f}; + float width_tolerance{0.1f}; +}; + +class Cut { + + Model m_model; + int m_instance; + const Transform3d m_cut_matrix; + ModelObjectCutAttributes m_attributes; + + void post_process(ModelObject *object, bool is_upper, ModelObjectPtrs &objects, bool keep, bool place_on_cut, bool flip); + void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects); + void finalize(const ModelObjectPtrs& objects); + +public: + + Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix, + ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper | + ModelObjectCutAttribute::KeepLower | + ModelObjectCutAttribute::CutToParts ); + ~Cut() { m_model.clear_objects(); } + + struct Part + { + bool selected; + bool is_modifier; + }; + + const ModelObjectPtrs& perform_with_plane(); + const ModelObjectPtrs& perform_by_contour(std::vector parts, int dowels_count); + const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false); + bool set_offset_for_two_part{false}; +}; // namespace Cut + +} // namespace Slic3r + +#endif /* slic3r_CutUtils_hpp_ */ diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3385bd9aa9..060945b7e5 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2783,6 +2783,12 @@ void ModelVolume::set_material_id(t_model_material_id material_id) this->object->get_model()->add_material(material_id); } +void ModelVolume::reset_extra_facets() { + this->supported_facets.reset(); + this->seam_facets.reset(); + this->mmu_segmentation_facets.reset(); +} + ModelMaterial* ModelVolume::material() const { return this->object->get_model()->get_material(m_material_id); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index b0473f2f38..0b9ce88782 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -848,6 +848,7 @@ public: // It contains information about connetors struct CutInfo { + bool is_from_upper{true}; bool is_connector{false}; bool is_processed{true}; CutConnectorType connector_type{CutConnectorType::Plug}; @@ -864,11 +865,14 @@ public: void set_processed() { is_processed = true; } void invalidate() { is_connector = false; } - + void reset_from_upper() { is_from_upper = true; } template inline void serialize(Archive &ar) { ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); } }; CutInfo cut_info; + bool is_from_upper() const { return cut_info.is_from_upper; } + void reset_from_upper() { cut_info.reset_from_upper(); } + bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } void invalidate_cut_info() { cut_info.invalidate(); } @@ -914,6 +918,7 @@ public: bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; } t_model_material_id material_id() const { return m_material_id; } void set_material_id(t_model_material_id material_id); + void reset_extra_facets(); ModelMaterial* material() const; void set_material(t_model_material_id material_id, const ModelMaterial &material); // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index b13fc538d3..f93d163259 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -8,6 +8,7 @@ #include "Point.hpp" #include "Execution/ExecutionTBB.hpp" #include "Execution/ExecutionSeq.hpp" +#include "CutUtils.hpp" #include "Utils.hpp" #include "Format/STL.hpp" #include @@ -1234,6 +1235,325 @@ indexed_triangle_set its_make_snap(double r, double h, float space_proportion, f return mesh; } +indexed_triangle_set its_make_groove_plane(const Groove &cur_groove, float rotate_radius, std::vector &cur_groove_vertices) { + // values for calculation + const float side_width = is_approx(cur_groove.flaps_angle, 0.f) ? cur_groove.depth : (cur_groove.depth / sin(cur_groove.flaps_angle)); + const float flaps_width = 2.f * side_width * cos(cur_groove.flaps_angle); + + const float groove_half_width_upper = 0.5f * (cur_groove.width); + const float groove_half_width_lower = 0.5f * (cur_groove.width + flaps_width); + + const float cut_plane_radius = 1.5f * float(rotate_radius); + const float cut_plane_length = 1.5f * cut_plane_radius; + + const float groove_half_depth = 0.5f * cur_groove.depth; + + const float x = 0.5f * cut_plane_radius; + const float y = 0.5f * cut_plane_length; + float z_upper = groove_half_depth; + float z_lower = -groove_half_depth; + + const float proj = y * tan(cur_groove.angle); + + float ext_upper_x = groove_half_width_upper + proj; // upper_x extension + float ext_lower_x = groove_half_width_lower + proj; // lower_x extension + + float nar_upper_x = groove_half_width_upper - proj; // upper_x narrowing + float nar_lower_x = groove_half_width_lower - proj; // lower_x narrowing + + const float cut_plane_thiknes = 0.02f; // 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut_plane_thiknes + + // Vertices of the groove used to detection if groove is valid + // They are written as: + // {left_ext_lower, left_nar_lower, left_ext_upper, left_nar_upper, + // right_ext_lower, right_nar_lower, right_ext_upper, right_nar_upper } + { + cur_groove_vertices.clear(); + cur_groove_vertices.reserve(8); + + cur_groove_vertices.emplace_back(Vec3f(-ext_lower_x, -y, z_lower).cast()); + cur_groove_vertices.emplace_back(Vec3f(-nar_lower_x, y, z_lower).cast()); + cur_groove_vertices.emplace_back(Vec3f(-ext_upper_x, -y, z_upper).cast()); + cur_groove_vertices.emplace_back(Vec3f(-nar_upper_x, y, z_upper).cast()); + cur_groove_vertices.emplace_back(Vec3f(ext_lower_x, -y, z_lower).cast()); + cur_groove_vertices.emplace_back(Vec3f(nar_lower_x, y, z_lower).cast()); + cur_groove_vertices.emplace_back(Vec3f(ext_upper_x, -y, z_upper).cast()); + cur_groove_vertices.emplace_back(Vec3f(nar_upper_x, y, z_upper).cast()); + } + // Different cases of groove plane: + // groove is open + if (groove_half_width_upper > proj && groove_half_width_lower > proj) { + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float nar_upper_x, float nar_lower_x, float ext_upper_x, float ext_lower_x) { + return std::vector({// upper left part vertices + {-x, -y, z_upper}, + {-x, y, z_upper}, + {-nar_upper_x, y, z_upper}, + {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, + {-nar_lower_x, y, z_lower}, + {nar_lower_x, y, z_lower}, + {ext_lower_x, -y, z_lower}, + // upper right part vertices + {ext_upper_x, -y, z_upper}, + {nar_upper_x, y, z_upper}, + {x, y, z_upper}, + {x, -y, z_upper}}); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * cur_groove.flaps_angle); + + nar_upper_x += under_x_shift; + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, nar_upper_x, nar_lower_x, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = {// above view + {5, 4, 7}, + {5, 7, 6}, // lower part + {3, 4, 5}, + {3, 5, 2}, // left side + {9, 6, 8}, + {8, 6, 7}, // right side + {1, 0, 2}, + {2, 0, 3}, // upper left part + {9, 8, 10}, + {10, 8, 11}, // upper right part + // under view + {20, 21, 22}, + {20, 22, 23}, // upper right part + {12, 13, 14}, + {12, 14, 15}, // upper left part + {18, 21, 20}, + {18, 20, 19}, // right side + {16, 15, 14}, + {16, 14, 17}, // left side + {16, 17, 18}, + {16, 18, 19}, // lower part + // left edge + {1, 13, 0}, + {0, 13, 12}, + // front edge + {0, 12, 3}, + {3, 12, 15}, + {3, 15, 4}, + {4, 15, 16}, + {4, 16, 7}, + {7, 16, 19}, + {7, 19, 20}, + {7, 20, 8}, + {8, 20, 11}, + {11, 20, 23}, + // right edge + {11, 23, 10}, + {10, 23, 22}, + // back edge + {1, 13, 2}, + {2, 13, 14}, + {2, 14, 17}, + {2, 17, 5}, + {5, 17, 6}, + {6, 17, 18}, + {6, 18, 9}, + {9, 18, 21}, + {9, 21, 10}, + {10, 21, 22}}; + return mesh; + } + + float cross_pt_upper_y = groove_half_width_upper / tan(cur_groove.angle); + // groove is closed + + if (groove_half_width_upper < proj && groove_half_width_lower < proj) { + float cross_pt_lower_y = groove_half_width_lower / tan(cur_groove.angle); + + indexed_triangle_set mesh; + + auto get_vertices = [x, y](float z_upper, float z_lower, float cross_pt_upper_y, float cross_pt_lower_y, float ext_upper_x, float ext_lower_x) { + return std::vector({// upper part vertices + {-x, -y, z_upper}, + {-x, y, z_upper}, + {x, y, z_upper}, + {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, + {0.f, cross_pt_upper_y, z_upper}, + {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, + {0.f, cross_pt_lower_y, z_lower}, + {ext_lower_x, -y, z_lower}}); + }; + + mesh.vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.reserve(2 * mesh.vertices.size()); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * cur_groove.flaps_angle); + + cross_pt_upper_y += cut_plane_thiknes; + cross_pt_lower_y += cut_plane_thiknes; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = get_vertices(z_upper, z_lower, cross_pt_upper_y, cross_pt_lower_y, ext_upper_x, ext_lower_x); + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + + mesh.indices = { // above view + {8, 7, 9}, // lower part + {5, 8, 6}, + {6, 8, 7}, // left side + {4, 9, 8}, + {4, 8, 5}, // right side + {1, 0, 6}, + {1, 6, 5}, + {1, 5, 2}, + {2, 5, 4}, + {2, 4, 3}, // upper part + // under view + {10, 11, 16}, + {16, 11, 15}, + {15, 11, 12}, + {15, 12, 14}, + {14, 12, 13}, // upper part + {18, 15, 14}, + {14, 18, 19}, // right side + {17, 16, 15}, + {17, 15, 18}, // left side + {17, 18, 19}, // lower part + // left edge + {1, 11, 0}, + {0, 11, 10}, + // front edge + {0, 10, 6}, + {6, 10, 16}, + {6, 17, 16}, + {6, 7, 17}, + {7, 17, 19}, + {7, 19, 9}, + {4, 14, 19}, + {4, 19, 9}, + {4, 14, 13}, + {4, 13, 3}, + // right edge + {3, 13, 12}, + {3, 12, 2}, + // back edge + {2, 12, 11}, + {2, 11, 1}}; + + return mesh; + } + + // groove is closed from the roof + indexed_triangle_set mesh; + mesh.vertices = {// upper part vertices + {-x, -y, z_upper}, + {-x, y, z_upper}, + {x, y, z_upper}, + {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, + {0.f, cross_pt_upper_y, z_upper}, + {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, + {-nar_lower_x, y, z_lower}, + {nar_lower_x, y, z_lower}, + {ext_lower_x, -y, z_lower}}; + + mesh.vertices.reserve(2 * mesh.vertices.size() + 1); + + z_upper -= cut_plane_thiknes; + z_lower -= cut_plane_thiknes; + + const float under_x_shift = cut_plane_thiknes / tan(0.5f * cur_groove.flaps_angle); + + nar_lower_x += under_x_shift; + ext_upper_x += under_x_shift; + ext_lower_x += under_x_shift; + + std::vector vertices = {// upper part vertices + {-x, -y, z_upper}, + {-x, y, z_upper}, + {x, y, z_upper}, + {x, -y, z_upper}, + {ext_upper_x, -y, z_upper}, + {under_x_shift, cross_pt_upper_y, z_upper}, + {-under_x_shift, cross_pt_upper_y, z_upper}, + {-ext_upper_x, -y, z_upper}, + // lower part vertices + {-ext_lower_x, -y, z_lower}, + {-nar_lower_x, y, z_lower}, + {nar_lower_x, y, z_lower}, + {ext_lower_x, -y, z_lower}}; + mesh.vertices.insert(mesh.vertices.end(), vertices.begin(), vertices.end()); + mesh.indices = {// above view + {8, 7, 10}, + {8, 10, 9}, // lower part + {5, 8, 7}, + {5, 7, 6}, // left side + {4, 10, 9}, + {4, 9, 5}, // right side + {1, 0, 6}, + {1, 6, 5}, + {1, 5, 2}, + {2, 5, 4}, + {2, 4, 3}, // upper part + // under view + {11, 12, 18}, + {18, 12, 17}, + {17, 12, 16}, + {16, 12, 13}, + {16, 13, 15}, + {15, 13, 14}, // upper part + {21, 16, 15}, + {21, 15, 22}, // right side + {19, 18, 17}, + {19, 17, 20}, // left side + {19, 20, 21}, + {19, 21, 22}, // lower part + // left edge + {1, 12, 11}, + {1, 11, 0}, + // front edge + {0, 11, 18}, + {0, 18, 6}, + {7, 19, 18}, + {7, 18, 6}, + {7, 19, 22}, + {7, 22, 10}, + {10, 22, 15}, + {10, 15, 4}, + {4, 15, 14}, + {4, 14, 3}, + // right edge + {3, 14, 13}, + {3, 14, 2}, + // back edge + {2, 13, 12}, + {2, 12, 1}, + {5, 16, 21}, + {5, 21, 9}, + {9, 21, 20}, + {9, 20, 8}, + {5, 17, 20}, + {5, 20, 8}}; + + return mesh; +} + indexed_triangle_set its_convex_hull(const std::vector &pts) { std::vector dst_vertices; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 9d878b0b7b..ebb1d57be3 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -15,7 +15,7 @@ namespace Slic3r { class TriangleMesh; class TriangleMeshSlicer; - +struct Groove; struct RepairedMeshErrors { // How many edges were united by merging their end points with some other end points in epsilon neighborhood? int edges_fixed = 0; @@ -341,6 +341,7 @@ indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCou indexed_triangle_set its_make_pyramid(float base, float height); indexed_triangle_set its_make_sphere(double radius, double fa); indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f); +indexed_triangle_set its_make_groove_plane(const Groove &cur_groove, float rotate_radius, std::vector &cur_groove_vertices); indexed_triangle_set its_convex_hull(const std::vector &pts); inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 3c1a7b04de..4bb491d899 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -318,7 +318,7 @@ public: const IndicesList& get_volume_idxs() const { return m_list; } const GLVolume* get_volume(unsigned int volume_idx) const; - + const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); } const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } unsigned int volumes_count() const { return (unsigned int)m_list.size(); }