BambuStudio/libslic3r/CutUtils.cpp

669 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "CutUtils.hpp"
#include "Geometry.hpp"
#include "libslic3r.h"
#include "Model.hpp"
#include "TriangleMeshSlicer.hpp"
#include "TriangleSelector.hpp"
#include "ObjectID.hpp"
#include <boost/log/trivial.hpp>
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<double>(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<ModelObject *> &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<float>());
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
// 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<ModelObject *> 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<Part> 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 dont 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