#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); if (upper && lower &&!m_attributes.has(ModelObjectCutAttribute::CutToParts)) { upper->name = upper->name + "_A"; lower->name = lower->name + "_B"; } 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); if (upper && lower) { upper->name = upper->name + "_A"; lower->name = lower->name + "_B"; } 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); // Now merge all model parts together: merge_solid_parts_inside_object(cut_object_ptrs); finalize(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); // Now merge all model parts together: merge_solid_parts_inside_object(cut_object_ptrs); finalize(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++) m_model.add_object(*cut_connectors_obj[id]); } 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); if (upper && lower) { upper->name = upper->name + "_A"; lower->name = lower->name + "_B"; } 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