CutGizmo: Big code refactoring.

All manipulations related to cut are extracted to CutUtils now
This commit is contained in:
YuSanka 2023-08-09 13:22:11 +02:00
parent 190a4cde48
commit 1b451cdf9f
10 changed files with 781 additions and 718 deletions

View File

@ -40,6 +40,7 @@
#include "libslic3r/Geometry.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/CutUtils.hpp"
#include "libslic3r/ModelArrange.hpp"
#include "libslic3r/Platform.hpp"
#include "libslic3r/Print.hpp"
@ -437,8 +438,11 @@ int CLI::run(int argc, char **argv)
}
#else
// model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower);
model.objects.front()->cut(0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
Cut cut(model.objects.front(), 0, Geometry::translation_transform(m_config.opt_float("cut") * Vec3d::UnitZ()),
ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::PlaceOnCutUpper);
auto cut_objects = cut.perform_with_plane();
for (ModelObject* obj : cut_objects)
model.add_object(*obj);
#endif
model.delete_object(size_t(0));
}

View File

@ -201,6 +201,8 @@ set(SLIC3R_SOURCES
BlacklistedLibraryCheck.hpp
LocalesUtils.cpp
LocalesUtils.hpp
CutUtils.cpp
CutUtils.hpp
Model.cpp
Model.hpp
ModelArrange.hpp

627
src/libslic3r/CutUtils.cpp Normal file
View File

@ -0,0 +1,627 @@
#include "CutUtils.hpp"
#include "libslic3r.h"
#include "Model.hpp"
#include "TriangleMeshSlicer.hpp"
#include "TriangleSelector.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::KeepAsParts)) {
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::KeepAsParts)) {
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
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)
{
// 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();
obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()));
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::KeepAsParts*/)
: m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes)
{
m_model = Model();
if (object)
m_model.add_object(*object);
}
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::KeepAsParts))
mo->clone_for_cut(&lower);
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::KeepAsParts) && 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::KeepAsParts) && !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);
}
};
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
delete_extra_modifiers(upper);
reset_instance_transformation(upper, m_instance, m_cut_matrix,
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
m_attributes.has(ModelObjectCutAttribute::FlipUpper));
cut_object_ptrs.push_back(upper);
}
if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
delete_extra_modifiers(lower);
reset_instance_transformation(lower, m_instance, m_cut_matrix,
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
cut_object_ptrs.push_back(lower);
}
if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
for (auto dowel : dowels) {
reset_instance_transformation(dowel, m_instance, Transform3d::Identity());
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
cut_object_ptrs.push_back(dowel);
}
}
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
m_model.clear_objects();
m_model.objects = 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);
auto add_cut_objects = [this](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) {
if (upper && !upper->volumes.empty()) {
reset_instance_transformation(upper, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper));
cut_objects.push_back(upper);
}
if (lower && !lower->volumes.empty()) {
reset_instance_transformation(lower, m_instance, m_cut_matrix, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
cut_objects.push_back(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
add_cut_objects(cut_object_ptrs, upper, lower);
}
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
add_cut_objects(cut_object_ptrs, upper, lower);
// 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);
m_model.clear_objects();
m_model.objects = 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());
reset_instance_transformation(upper, m_instance, cut_matrix_upper, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper), m_attributes.has(ModelObjectCutAttribute::FlipUpper));
cut_object_ptrs.push_back(upper);
reset_instance_transformation(lower, m_instance, cut_matrix_lower, m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower), m_attributes.has(ModelObjectCutAttribute::FlipUpper));
cut_object_ptrs.push_back(lower);
// Now merge all model parts together:
merge_solid_parts_inside_object(cut_object_ptrs);
}
m_model.clear_objects();
m_model.objects = cut_object_ptrs;
return m_model.objects;
}
} // namespace Slic3r

View File

@ -0,0 +1,65 @@
#ifndef slic3r_CutUtils_hpp_
#define slic3r_CutUtils_hpp_
#include "libslic3r.h"
#include "enum_bitmask.hpp"
#include "Geometry.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
#include "Model.hpp"
#include <vector>
namespace Slic3r {
using ModelObjectPtrs = std::vector<ModelObject*>;
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
class Cut {
Model m_model;
int m_instance;
const Transform3d& m_cut_matrix;
ModelObjectCutAttributes m_attributes;
public:
Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper |
ModelObjectCutAttribute::KeepLower |
ModelObjectCutAttribute::KeepAsParts );
~Cut() { m_model.clear_objects(); }
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 };
};
struct Part
{
bool selected;
bool is_modifier;
};
const ModelObjectPtrs& perform_with_plane();
const ModelObjectPtrs& perform_by_contour(std::vector<Part> parts, int dowels_count);
const ModelObjectPtrs& perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts = false);
}; // namespace Cut
} // namespace Slic3r
#endif /* slic3r_CutUtils_hpp_ */

View File

@ -1332,327 +1332,6 @@ void ModelVolume::reset_extra_facets()
this->mmu_segmentation_facets.reset();
}
void ModelVolume::apply_tolerance()
{
assert(cut_info.is_connector);
if (!cut_info.is_processed)
return;
Vec3d sf = 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);
set_scaling_factor(sf);
// correct offset in respect to the new depth
Vec3d rot_norm = Geometry::rotation_transform(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
set_offset(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;
}
void ModelObject::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);
vol->apply_tolerance();
}
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.
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
volume->apply_tolerance();
// 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());
}
}
void ModelObject::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(Geometry::Transformation(volume_matrix));
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
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);
}
void ModelObject::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();
using namespace Geometry;
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);
}
void ModelObject::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::KeepAsParts)) {
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
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);
}
void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut/* = false*/, bool flip/* = false*/)
{
using namespace Geometry;
// 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();
obj_instance->set_transformation(Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor()));
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);
}
}
ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
{
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
return {};
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
clone_for_cut(&upper);
ModelObject* lower{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::KeepAsParts))
clone_for_cut(&lower);
std::vector<ModelObject*> dowels;
using namespace Geometry;
// 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 = instances[instance]->get_matrix(true);
const auto instance_matrix = instances[instance]->get_transformation().get_matrix_no_offset();
const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
for (ModelVolume* volume : 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, attributes, upper, lower);
else
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels);
}
else if (!volume->mesh().empty())
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower);
}
// Post-process cut parts
ModelObjectPtrs res;
if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty())
return res;
if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
reset_instance_transformation(upper, instance, cut_matrix);
res.push_back(upper);
}
else {
// Delete all modifiers which are not intersecting with solid parts bounding box
auto delete_extra_modifiers = [instance](ModelObject* mo) {
if (!mo) return;
const BoundingBoxf3 obj_bb = mo->instance_bounding_box(instance);
const Transform3d inst_matrix = mo->instances[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);
}
};
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
delete_extra_modifiers(upper);
reset_instance_transformation(upper, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
attributes.has(ModelObjectCutAttribute::FlipUpper));
res.push_back(upper);
}
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
delete_extra_modifiers(lower);
reset_instance_transformation(lower, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower));
res.push_back(lower);
}
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
for (auto dowel : dowels) {
reset_instance_transformation(dowel, instance, Transform3d::Identity());
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
res.push_back(dowel);
}
}
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
return res;
}
/// <summary>
/// Compare TriangleMeshes by Bounding boxes (mainly for sort)

View File

@ -317,10 +317,6 @@ enum class ModelVolumeType : int {
SUPPORT_ENFORCER,
};
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
@ -468,21 +464,6 @@ public:
void delete_connectors();
void clone_for_cut(ModelObject **obj);
private:
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels);
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
public:
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);
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs*new_objects);
void merge();
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
@ -850,7 +831,6 @@ public:
bool is_the_only_one_part() const; // behave like an object
t_model_material_id material_id() const { return m_material_id; }
void reset_extra_facets();
void apply_tolerance();
void set_material_id(t_model_material_id material_id);
ModelMaterial* material() const;
void set_material(t_model_material_id material_id, const ModelMaterial &material);

View File

@ -675,7 +675,7 @@ static double get_grabber_mean_size(const BoundingBoxf3& bb)
indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane()
{
const float ghw = 0.5f * m_groove_width; // groove half width
const float ghw = 0.5f * m_groove.width; // groove half width
const float cpr = 1.5f * float(m_radius); // cut plane radius
const float cpl = 1.5f * cpr; // cut plane length
@ -684,7 +684,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane()
// We need two upward facing triangles
float x = 0.5f * cpr, y = 0.5f * cpl;
const float proj = y * tan(m_groove_angle);
const float proj = y * tan(m_groove.angle);
const float extension_x = ghw + proj;
// upper cut plane is simple
@ -712,7 +712,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane()
if (ghw < proj) {
const float cross_pt_y = ghw / tan(m_groove_angle);
const float cross_pt_y = ghw / tan(m_groove.angle);
return {
{
// roof
@ -756,7 +756,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_upper_groove_plane()
indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width)
{
const float ghw = 0.5f * (m_groove_width + flaps_width); // groove half width
const float ghw = 0.5f * (m_groove.width + flaps_width); // groove half width
const float cpr = 1.5f * float(m_radius); // cut plane radius
const float cpl = 1.5f * cpr; // cut plane length
@ -765,7 +765,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width
// We need two upward facing triangles
float x = 0.5f * cpr, y = 0.5f * cpl;
const float proj = y * tan(m_groove_angle);
const float proj = y * tan(m_groove.angle);
const float extension_x = ghw + proj;
// upper cut plane is trapezium
@ -793,7 +793,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width
// upper cut plane is triangle
const float cross_pt_y = ghw / tan(m_groove_angle);
const float cross_pt_y = ghw / tan(m_groove.angle);
return {
{
// roof
@ -814,14 +814,14 @@ indexed_triangle_set GLGizmoCut3D::its_make_lower_groove_plane(float flaps_width
indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width)
{
const float ghw_upper = 0.5f * (m_groove_width); // groove half width
const float ghw_lower = 0.5f * (m_groove_width + flaps_width); // groove half width
const float ghw_upper = 0.5f * (m_groove.width); // groove half width
const float ghw_lower = 0.5f * (m_groove.width + flaps_width); // groove half width
const float cpr = 1.5f * float(m_radius); // cut plane radius
const float cpl = 1.5f * cpr; // cut plane length
const float cph = 0.02f * (float)get_grabber_mean_size(m_bounding_box); // cut plane height
const float ghd = 0.5f * m_groove_depth; // groove half depth
const float ghd = 0.5f * m_groove.depth; // groove half depth
// We need two upward facing triangles
@ -829,7 +829,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width
float z_upper = ghd;
float z_lower = -ghd;
const float proj = y * tan(m_groove_angle);
const float proj = y * tan(m_groove.angle);
const float extension_upper_x = ghw_upper + proj;
const float extension_lower_x = ghw_lower + proj;
@ -854,12 +854,12 @@ indexed_triangle_set GLGizmoCut3D::its_make_sides_groove_plane(float flaps_width
};
}
const float cross_pt_upper_y = ghw_upper / tan(m_groove_angle);
const float cross_pt_upper_y = ghw_upper / tan(m_groove.angle);
// groove is closed
if (ghw_upper < proj && ghw_lower < proj) {
const float cross_pt_lower_y = ghw_lower / tan(m_groove_angle);
const float cross_pt_lower_y = ghw_lower / tan(m_groove.angle);
return {
{
@ -898,23 +898,23 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane()
{
// values for calculation
const float side_width = is_approx(m_groove_flaps_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_flaps_angle));
const float flaps_width = 2.f * side_width * cos(m_groove_flaps_angle);
const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle));
const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle);
const float groove_half_width_upper = 0.5f * (m_groove_width);
const float groove_half_width_lower = 0.5f * (m_groove_width + flaps_width);
const float groove_half_width_upper = 0.5f * (m_groove.width);
const float groove_half_width_lower = 0.5f * (m_groove.width + flaps_width);
const float cut_plane_radius = 1.5f * float(m_radius);
const float cut_plane_length = 1.5f * cut_plane_radius;
const float groove_half_depth = 0.5f * m_groove_depth;
const float groove_half_depth = 0.5f * m_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(m_groove_angle);
const float proj = y * tan(m_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
@ -966,7 +966,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane()
z_upper -= cut_plane_thiknes;
z_lower -= cut_plane_thiknes;
if (m_use_TAG_mesh_full) {
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle);
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
nar_upper_x += under_x_shift;
nar_lower_x += under_x_shift;
@ -1019,12 +1019,12 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane()
return mesh;
}
float cross_pt_upper_y = groove_half_width_upper / tan(m_groove_angle);
float cross_pt_upper_y = groove_half_width_upper / tan(m_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(m_groove_angle);
float cross_pt_lower_y = groove_half_width_lower / tan(m_groove.angle);
indexed_triangle_set mesh;
@ -1045,7 +1045,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane()
z_lower -= cut_plane_thiknes;
if (m_use_TAG_mesh_full) {
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle);
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
cross_pt_upper_y += cut_plane_thiknes;
cross_pt_lower_y += cut_plane_thiknes;
@ -1112,7 +1112,7 @@ indexed_triangle_set GLGizmoCut3D::its_make_groove_plane()
z_upper -= cut_plane_thiknes;
z_lower -= cut_plane_thiknes;
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove_flaps_angle);
const float under_x_shift = cut_plane_thiknes / tan(0.5f * m_groove.flaps_angle);
nar_lower_x += under_x_shift;
ext_upper_x += under_x_shift;
@ -1173,10 +1173,10 @@ void GLGizmoCut3D::render_cut_plate_for_tongue_and_groove(GLShaderProgram* shade
ColorRGBA cp_clr = m_plane.model.get_color();
// values for calculaton
const double groove_half_depth = 0.5 * double(m_groove_depth);
const double groove_half_depth = 0.5 * double(m_groove.depth);
const float side_width = is_approx(m_groove_flaps_angle, 0.f) ? m_groove_depth : (m_groove_depth / sin(m_groove_flaps_angle));
const float flaps_width = 2.f * side_width * cos(m_groove_flaps_angle);
const float side_width = is_approx(m_groove.flaps_angle, 0.f) ? m_groove.depth : (m_groove.depth / sin(m_groove.flaps_angle));
const float flaps_width = 2.f * side_width * cos(m_groove.flaps_angle);
GLModel model;
@ -1454,7 +1454,7 @@ void GLGizmoCut3D::render_cut_plane_grabbers()
// render CutPlaneYMove grabber
if (m_groove_angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove))
if (m_groove.angle > 0.0f && (no_xy_grabber_hovered || m_hover_id == CutPlaneYMove))
{
size = (m_dragging ? get_dragging_half_size(mean_size) : get_half_size(mean_size));
color = m_hover_id == CutPlaneYMove ? ColorRGBA::GREEN() : m_plane.model.get_color();
@ -1526,19 +1526,19 @@ void GLGizmoCut3D::on_load(cereal::BinaryInputArchive& ar)
if (m_mode != mode)
switch_to_mode(mode);
else if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
if (!is_approx(m_groove_depth , groove_depth) ||
!is_approx(m_groove_width , groove_width) ||
!is_approx(m_groove_flaps_angle , groove_flaps_angle) ||
!is_approx(m_groove_angle , groove_angle) ||
!is_approx(m_groove_depth_tolerance, groove_depth_tolerance) ||
!is_approx(m_groove_width_tolerance, groove_width_tolerance) )
if (!is_approx(m_groove.depth , groove_depth) ||
!is_approx(m_groove.width , groove_width) ||
!is_approx(m_groove.flaps_angle , groove_flaps_angle) ||
!is_approx(m_groove.angle , groove_angle) ||
!is_approx(m_groove.depth_tolerance, groove_depth_tolerance) ||
!is_approx(m_groove.width_tolerance, groove_width_tolerance) )
{
m_groove_depth = groove_depth;
m_groove_width = groove_width;
m_groove_flaps_angle = groove_flaps_angle;
m_groove_angle = groove_angle;
m_groove_depth_tolerance= groove_depth_tolerance;
m_groove_width_tolerance= groove_width_tolerance;
m_groove.depth = groove_depth;
m_groove.width = groove_width;
m_groove.flaps_angle = groove_flaps_angle;
m_groove.angle = groove_angle;
m_groove.depth_tolerance= groove_depth_tolerance;
m_groove.width_tolerance= groove_width_tolerance;
update_plane_model();
}
reset_cut_by_contours();
@ -1551,7 +1551,7 @@ void GLGizmoCut3D::on_save(cereal::BinaryOutputArchive& ar) const
{
ar( m_keep_upper, m_keep_lower, m_rotate_lower, m_rotate_upper, m_hide_cut_plane, m_mode, m_connectors_editing,
m_ar_plane_center, m_start_dragging_m,
m_groove_depth, m_groove_width, m_groove_flaps_angle, m_groove_angle, m_groove_depth_tolerance, m_groove_width_tolerance);
m_groove.depth, m_groove.width, m_groove.flaps_angle, m_groove.angle, m_groove.depth_tolerance, m_groove.width_tolerance);
}
std::string GLGizmoCut3D::on_get_name() const
@ -1755,7 +1755,7 @@ void GLGizmoCut3D::update_raycasters_for_picking_transform()
offset = (size + xy_connection_len) * Vec3d::UnitX();
m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * rotation_transform(0.5 * PI * Vec3d::UnitY()) * scale_transform(cone_scale));
if (m_groove_angle > 0.0f) {
if (m_groove.angle > 0.0f) {
offset = xy_connection_len * Vec3d::UnitY() - 0.5 * size * Vec3d::Ones();
m_raycasters[id++]->set_transform(trafo * translation_transform(offset) * scale_transform(size));
offset = (size + xy_connection_len) * Vec3d::UnitY();
@ -1996,7 +1996,7 @@ void GLGizmoCut3D::set_center_pos(const Vec3d& center_pos, bool update_tbb /*=fa
bool can_set_center_pos = false;
{
double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove_depth) : */0.5;
double limit_val = /*CutMode(m_mode) == CutMode::cutTongueAndGroove ? 0.5 * double(m_groove.depth) : */0.5;
if (tbb.max.z() > -limit_val && tbb.min.z() < limit_val)
can_set_center_pos = true;
else {
@ -2093,10 +2093,10 @@ void GLGizmoCut3D::update_bb()
m_snap_fine_out_radius = m_grabber_connection_len * 1.15;
// input params for cut with tongue and groove
m_groove_depth = m_groove_depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box)));
m_groove_width = m_groove_width_init = 4.0f * m_groove_depth;
m_groove_flaps_angle = m_groove_flaps_angle_init = float(PI) / 3.f;
m_groove_angle = m_groove_angle_init = 0.f;
m_groove.depth = m_groove.depth_init = std::max(1.f , 0.5f * float(get_grabber_mean_size(m_bounding_box)));
m_groove.width = m_groove.width_init = 4.0f * m_groove.depth;
m_groove.flaps_angle = m_groove.flaps_angle_init = float(PI) / 3.f;
m_groove.angle = m_groove.angle_init = 0.f;
m_plane.reset();
m_cone.reset();
m_sphere.reset();
@ -2188,16 +2188,9 @@ void GLGizmoCut3D::render_clipper_cut()
GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx_in, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc)
{
Cut cut(mo, instance_idx_in, cut_matrix);
m_model = Model();
m_model.add_object(*mo);
Model tmp_model = Model();
tmp_model.objects = m_model.objects.front()->cut(instance_idx_in, cut_matrix,
ModelObjectCutAttribute::KeepUpper |
ModelObjectCutAttribute::KeepLower |
ModelObjectCutAttribute::KeepAsParts);
assert(tmp_model.objects.size() == 1);
m_model = tmp_model;
m_model.add_object(*cut.perform_with_plane().front());
m_instance_idx = instance_idx_in;
@ -2270,6 +2263,7 @@ GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* mo, const Transfor
m_valid = true;
}
// In CutMode::cutTongueAndGroove we use PartSelection just for rendering
GLGizmoCut3D::PartSelection::PartSelection(const ModelObject* object, int instance_idx_in)
{
m_instance_idx = instance_idx_in;
@ -2388,6 +2382,16 @@ bool GLGizmoCut3D::PartSelection::is_one_object() const
});
}
std::vector<Cut::Part> GLGizmoCut3D::PartSelection::get_cut_parts()
{
std::vector<Cut::Part> parts;
for (const auto& part : m_parts)
parts.push_back({part.selected, part.is_modifier});
return parts;
}
void GLGizmoCut3D::PartSelection::toggle_selection(const Vec2d& mouse_pos)
{
@ -2795,11 +2799,12 @@ void GLGizmoCut3D::process_contours()
wxBusyCursor wait;
if (CutMode(m_mode) == CutMode::cutTongueAndGroove) {
Model tmp_model = Model();
tmp_model.objects = perform_cut_with_groove(model_objects[object_idx], true);
if (!tmp_model.objects.empty())
m_part_selection = PartSelection(tmp_model.objects.front(), instance_idx);
tmp_model = Model();
if (has_valid_groove()) {
Cut cut(model_objects[object_idx], instance_idx, get_cut_matrix(selection));
const ModelObjectPtrs& new_objects = cut.perform_with_groove(m_groove, m_rotation_m, true);
if (!new_objects.empty())
m_part_selection = PartSelection(new_objects.front(), instance_idx);
}
}
else {
reset_cut_by_contours();
@ -2989,10 +2994,10 @@ void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors)
else if (mode == CutMode::cutTongueAndGroove) {
ImGui::Separator();
ImGuiWrapper::text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, m_labels_map["Groove"] + ": ");
render_groove_float_input(m_labels_map["Depth"], m_groove_depth, m_groove_depth_init, m_groove_depth_tolerance);
render_groove_float_input(m_labels_map["Width"], m_groove_width, m_groove_width_init, m_groove_width_tolerance);
render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove_flaps_angle, m_groove_flaps_angle_init, 30.f, 120.f);
render_groove_angle_input(m_labels_map["Groove Angle"], m_groove_angle, m_groove_angle_init, 0.f, 15.f);
render_groove_float_input(m_labels_map["Depth"], m_groove.depth, m_groove.depth_init, m_groove.depth_tolerance);
render_groove_float_input(m_labels_map["Width"], m_groove.width, m_groove.width_init, m_groove.width_tolerance);
render_groove_angle_input(m_labels_map["Flaps Angle"], m_groove.flaps_angle, m_groove.flaps_angle_init, 30.f, 120.f);
render_groove_angle_input(m_labels_map["Groove Angle"], m_groove.angle, m_groove.angle_init, 0.f, 15.f);
// m_imgui->checkbox(_L("Optimize rendering"), m_optimaze_groove_rendering);
}
@ -3445,8 +3450,8 @@ bool GLGizmoCut3D::has_valid_groove() const
if (CutMode(m_mode) != CutMode::cutTongueAndGroove)
return true;
const float flaps_width = -2.f * m_groove_depth / tan(m_groove_flaps_angle);
if (flaps_width > m_groove_width)
const float flaps_width = -2.f * m_groove.depth / tan(m_groove.flaps_angle);
if (flaps_width > m_groove.width)
return false;
const Selection& selection = m_parent.get_selection();
@ -3553,280 +3558,6 @@ void synchronize_model_after_cut(Model& model, const CutObjectBase& cut_id)
obj->cut_id.copy(cut_id);
}
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);
}
}
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);
}
}
}
}
ModelObjectPtrs GLGizmoCut3D::perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count)
{
const Selection& selection = m_parent.get_selection();
const int instance_idx = selection.get_instance_idx();
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
if (m_keep_upper) cut_mo->clone_for_cut(&upper);
ModelObject* lower{ nullptr };
if (m_keep_lower) cut_mo->clone_for_cut(&lower);
const Transform3d cut_matrix = get_cut_matrix(selection);
auto add_cut_objects = [this, &instance_idx, &cut_matrix](ModelObjectPtrs& cut_objects, ModelObject* upper, ModelObject* lower) {
if (upper && !upper->volumes.empty()) {
ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix, m_place_on_cut_upper, m_rotate_upper);
cut_objects.push_back(upper);
}
if (lower && !lower->volumes.empty()) {
ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix, m_place_on_cut_lower, m_place_on_cut_lower || m_rotate_lower);
cut_objects.push_back(lower);
}
};
const size_t cut_parts_cnt = m_part_selection.parts().size();
bool has_modifiers = false;
// Distribute SolidParts to the Upper/Lower object
for (size_t id = 0; id < cut_parts_cnt; ++id) {
if (m_part_selection.parts()[id].is_modifier)
has_modifiers = true; // modifiers will be added later to the related parts
else if (ModelObject* obj = (m_part_selection.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, instance_idx, 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
add_cut_objects(cut_object_ptrs, upper, lower);
}
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
const ModelObjectPtrs cut_connectors_obj = cut_mo->cut(instance_idx, get_cut_matrix(selection), attributes);
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
add_cut_objects(cut_object_ptrs, upper, lower);
// 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);
return cut_object_ptrs;
}
ModelObjectPtrs GLGizmoCut3D::perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts/* = false*/)
{
if (!has_valid_groove())
return {};
const Selection& selection = m_parent.get_selection();
const int instance_idx = selection.get_instance_idx();
// 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(m_groove_depth);
const Transform3d cut_matrix = get_cut_matrix(selection);
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 = [instance_idx, add_volumes_from_cut]
(ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) {
Model model = Model();
model.add_object(*object);
tmp_model_for_cut = Model();
tmp_model_for_cut.objects = model.objects.front()->cut(instance_idx, cut_matrix, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts);
assert(!tmp_model_for_cut.objects.empty());
object->clear_volumes();
add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
ModelObject::reset_instance_transformation(object, instance_idx);
};
// cut by upper plane
const Transform3d cut_matrix_upper = translation_transform(m_rotation_m * (groove_half_depth * Vec3d::UnitZ())) * 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(m_rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * 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(m_groove_width + m_groove_depth / tan(m_groove_flaps_angle));
// cut by angle1 plane
{
const Transform3d cut_matrix_angle1 = translation_transform(m_rotation_m * (-h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_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(m_rotation_m * (h_side_shift * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_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)m_groove_depth_tolerance;
const Transform3d cut_matrix_lower_tolerance = translation_transform(m_rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * 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(m_groove_width_tolerance);
const Transform3d cut_matrix_angle1_tolerance = translation_transform(m_rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, -m_groove_flaps_angle, -m_groove_angle));
cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
const Transform3d cut_matrix_angle2_tolerance = translation_transform(m_rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * cut_matrix * rotation_transform(Vec3d(0, m_groove_flaps_angle, m_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, instance_idx, upper, lower);
break;
}
assert(!upper->volumes.empty() && !lower->volumes.empty());
ModelObject::reset_instance_transformation(upper, instance_idx, cut_matrix_upper, m_place_on_cut_upper, m_rotate_upper);
cut_object_ptrs.push_back(upper);
ModelObject::reset_instance_transformation(lower, instance_idx, cut_matrix_lower, m_place_on_cut_upper, m_rotate_upper);
cut_object_ptrs.push_back(lower);
// Now merge all model parts together:
merge_solid_parts_inside_object(cut_object_ptrs);
}
return cut_object_ptrs;
}
void GLGizmoCut3D::perform_cut(const Selection& selection)
{
if (!can_perform_cut())
@ -3867,8 +3598,6 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
wxBusyCursor wait;
const Transform3d cut_matrix = get_cut_matrix(selection);
ModelObjectCutAttributes attributes = only_if(has_connectors ? true : m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
only_if(has_connectors ? true : m_keep_lower, ModelObjectCutAttribute::KeepLower) |
only_if(has_connectors ? false : m_keep_as_parts, ModelObjectCutAttribute::KeepAsParts) |
@ -3882,16 +3611,15 @@ void GLGizmoCut3D::perform_cut(const Selection& selection)
// update cut_id for the cut object in respect to the attributes
update_object_cut_id(cut_mo->cut_id, attributes, dowels_count);
Model tmp_model = Model();
tmp_model.objects = cut_by_contour ? perform_cut_by_contour(cut_mo, attributes, dowels_count) :
cut_with_groove ? perform_cut_with_groove(cut_mo) :
cut_mo->cut(instance_idx, cut_matrix, attributes);
Cut cut(cut_mo, instance_idx, get_cut_matrix(selection), attributes);
const ModelObjectPtrs& new_objects = cut_by_contour ? cut.perform_by_contour(m_part_selection.get_cut_parts(), dowels_count):
cut_with_groove ? cut.perform_with_groove(m_groove, m_rotation_m) :
cut.perform_with_plane();
// save cut_id to post update synchronization
const CutObjectBase cut_id = cut_mo->cut_id;
// update cut results on plater and in the model
plater->cut(object_idx, tmp_model.objects);
plater->apply_cut_object_to_model(object_idx, new_objects);
synchronize_model_after_cut(plater->model(), cut_id);
}

View File

@ -7,6 +7,7 @@
#include "slic3r/GUI/I18N.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/CutUtils.hpp"
#include "imgui/imgui.h"
namespace Slic3r {
@ -118,16 +119,7 @@ class GLGizmoCut3D : public GLGizmoBase
bool m_rotate_lower{ false };
// Input params for cut with tongue and groove
float m_groove_depth;
float m_groove_width;
float m_groove_flaps_angle;
float m_groove_angle;
float m_groove_depth_init;
float m_groove_width_init;
float m_groove_flaps_angle_init;
float m_groove_angle_init;
float m_groove_depth_tolerance{ 0.1f };
float m_groove_width_tolerance{ 0.1f };
Cut::Groove m_groove;
bool m_optimaze_groove_rendering{ true };
// Input params for cut with snaps
@ -187,6 +179,8 @@ class GLGizmoCut3D : public GLGizmoBase
const std::vector<Part>& parts() const { return m_parts; }
const std::vector<size_t>* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
std::vector<Cut::Part> get_cut_parts();
private:
Model m_model;
int m_instance_idx;
@ -367,8 +361,6 @@ private:
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix, double line_len_koef = 1.0);
void render_cut_plane_grabbers();
void render_cut_line();
ModelObjectPtrs perform_cut_by_contour(ModelObject* cut_mo, const ModelObjectCutAttributes& attributes, int dowels_count);
ModelObjectPtrs perform_cut_with_groove(ModelObject* cut_mo, bool keep_as_parts = false);
void perform_cut(const Selection&selection);
void set_center_pos(const Vec3d&center_pos, bool update_tbb = false);
void update_bb();

View File

@ -6318,20 +6318,7 @@ void Plater::toggle_layers_editing(bool enable)
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
}
void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
{
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
auto* object = p->model.objects[obj_idx];
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
wxBusyCursor wait;
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
cut(obj_idx, new_objects);
}
void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects)
void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects)
{
model().delete_object(obj_idx);
sidebar().obj_list()->delete_object_from_list(obj_idx);

View File

@ -264,8 +264,7 @@ public:
void convert_unit(ConversionType conv_type);
void toggle_layers_editing(bool enable);
void cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes);
void cut(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
void export_gcode(bool prefer_removable);
void export_stl_obj(bool extended = false, bool selection_only = false);