mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-05 23:01:17 +00:00
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes. clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer. * Remove Unused Project Includes From Files With Platform-Specific Code A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block. * Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass The platform-file pass treated pchheader.hpp as an ordinary header and emptied it, and left GUI_Preview.hpp and 14 other files relying on headers they no longer reached directly. * Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call * Include Headers That Files Reached Through Ones the Cleanup Removed * Drop Includes Duplicated by the Cleanup or by Main's Own Additions * Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
796 lines
33 KiB
C++
796 lines
33 KiB
C++
|
||
#include "CutUtils.hpp"
|
||
#include "Geometry.hpp"
|
||
#include "Point.hpp"
|
||
#include "TriangleMesh.hpp"
|
||
#include "libslic3r.h"
|
||
#include "Model.hpp"
|
||
#include "TriangleMeshSlicer.hpp"
|
||
#include "TriangleSelector.hpp"
|
||
#include "ObjectID.hpp"
|
||
|
||
#include <algorithm>
|
||
#include <boost/log/trivial.hpp>
|
||
#include <cassert>
|
||
#include <string>
|
||
#include <vector>
|
||
#include <utility>
|
||
#include <cstddef>
|
||
#include <optional>
|
||
#include <math.h>
|
||
#include <cmath>
|
||
#include "BoundingBox.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( const 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(const 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");
|
||
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)
|
||
{
|
||
// 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);
|
||
|
||
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);
|
||
}
|
||
|
||
void Cut::post_process(ModelObject* object, 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);
|
||
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, cut_object_ptrs,
|
||
m_attributes.has(ModelObjectCutAttribute::KeepUpper),
|
||
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
|
||
m_attributes.has(ModelObjectCutAttribute::FlipUpper));
|
||
|
||
post_process(lower, 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, const std::vector<std::optional<TriangleSelector::SavedPainting>>& saved_paintings)
|
||
{
|
||
// Paint volumes
|
||
for (const auto& saved_painting : saved_paintings) {
|
||
if (saved_painting) {
|
||
for (const auto object : objects) {
|
||
for (const auto volume : object->volumes) {
|
||
if (volume->is_model_part() && !volume->is_cut_connector()) {
|
||
volume->restore_painting(saved_painting, true);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//clear model from temporary 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::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());
|
||
|
||
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
|
||
for (ModelVolume* volume : mo->volumes) {
|
||
// Save painting data before reset_extra_facets() discards it.
|
||
if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) {
|
||
saved_paintings.emplace_back(volume->save_painting());
|
||
if (saved_paintings.back()) {
|
||
// Transform mesh to cut space (same transform as process_volume_cut applies)
|
||
saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true);
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
};
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
finalize(cut_object_ptrs, saved_paintings);
|
||
|
||
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
|
||
|
||
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()) {
|
||
// Don't add modifiers which are processed connectors
|
||
if (vol->cut_info.is_connector && !vol->cut_info.is_processed)
|
||
continue;
|
||
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);
|
||
}
|
||
// Ensuring that volumes start with solid parts for proper slicing
|
||
mo->sort_volumes(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
const ModelObjectPtrs& Cut::perform_by_contour(const ModelObject* src_object, 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";
|
||
}
|
||
|
||
// Save painting data so we later can remap it.
|
||
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
|
||
if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) {
|
||
const auto instance_matrix = src_object->instances[m_instance]->get_transformation().get_matrix_no_offset();
|
||
for (const auto volume : src_object->volumes) {
|
||
saved_paintings.emplace_back(volume->save_painting());
|
||
if (saved_paintings.back()) {
|
||
// Transform mesh to cut space (same transform as process_volume_cut applies)
|
||
saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true);
|
||
}
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
// replace initial objects in model with cut object
|
||
finalize(cut_object_ptrs, saved_paintings);
|
||
}
|
||
else if (volumes.size() > cut_parts_cnt) {
|
||
// Means that object is cut with connectors
|
||
|
||
// All volumes are distributed to Upper / Lower object,
|
||
// So we don’t need them anymore
|
||
for (size_t id = 0; id < cut_parts_cnt; id++)
|
||
delete* (volumes.begin() + id);
|
||
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
|
||
|
||
// Perform cut just to get connectors
|
||
Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes);
|
||
const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane();
|
||
assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
|
||
|
||
// Connectors from upper object
|
||
for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
|
||
upper->add_volume(*volume, volume->type());
|
||
|
||
// Connectors from lower object
|
||
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
|
||
lower->add_volume(*volume, volume->type());
|
||
|
||
// Add Upper and Lower objects to cut_object_ptrs
|
||
post_process(upper, lower, cut_object_ptrs);
|
||
|
||
// Now merge all model parts together:
|
||
merge_solid_parts_inside_object(cut_object_ptrs);
|
||
|
||
// replace initial objects in model with cut object
|
||
finalize(cut_object_ptrs, saved_paintings);
|
||
|
||
// Add Dowel-connectors as separate objects to model
|
||
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,
|
||
const int groove_count,
|
||
const float groove_gap,
|
||
const float m_radius,
|
||
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";
|
||
}
|
||
|
||
// Save painting data so we later can remap it.
|
||
std::vector<std::optional<TriangleSelector::SavedPainting>> saved_paintings;
|
||
if (m_attributes.has(ModelObjectCutAttribute::KeepPaint)) {
|
||
const auto instance_matrix = cut_mo->instances[m_instance]->get_transformation().get_matrix_no_offset();
|
||
for (const auto volume : cut_mo->volumes) {
|
||
saved_paintings.emplace_back(volume->save_painting());
|
||
if (saved_paintings.back()) {
|
||
// Transform mesh to cut space (same transform as process_volume_cut applies)
|
||
saved_paintings.back()->mesh.transform(instance_matrix * volume->get_matrix(), true);
|
||
}
|
||
}
|
||
}
|
||
|
||
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 (+Z)
|
||
{
|
||
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 (-Z)
|
||
{
|
||
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);
|
||
}
|
||
|
||
// Compute same slot outer width used in preview plane
|
||
const float groove_width = calculate_groove_width(groove, m_radius);
|
||
|
||
ModelObject* groove_object{nullptr};
|
||
|
||
// multiple cuts
|
||
for (int i = 0; i < groove_count; i++) {
|
||
bool is_first_groove = i == 0;
|
||
bool is_last_groove = i == groove_count - 1;
|
||
|
||
// Calculate the x-axis offset for this dovetail
|
||
float groove_offset_factor_start = -.5 * ((groove_count - 1));
|
||
float groove_offset_factor = groove_offset_factor_start + i;
|
||
|
||
float offset_x = groove_offset_factor * (groove_gap + groove_width);
|
||
|
||
|
||
tmp_object->clone_for_cut(&groove_object);
|
||
for (ModelVolume* volume : tmp_object->volumes) {
|
||
ModelVolume* new_vol = groove_object->add_volume(*volume);
|
||
new_vol->reset_from_upper();
|
||
}
|
||
|
||
// isolate area of current groove
|
||
if (!is_first_groove) {
|
||
float left_cut_position = (-groove_gap / 2.f) - (groove_width / 2.f) + offset_x;
|
||
|
||
const Transform3d cut_matrix_left = translation_transform(rotation_m * (left_cut_position * Vec3d::UnitX())) *
|
||
m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0));
|
||
|
||
cut(groove_object, cut_matrix_left, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||
}
|
||
if (!is_last_groove) {
|
||
float right_cut_position = (groove_gap / 2.f) + (groove_width / 2.f) + offset_x;
|
||
|
||
const Transform3d cut_matrix_right = translation_transform(rotation_m * (right_cut_position * Vec3d::UnitX())) *
|
||
m_cut_matrix * rotation_transform(Vec3d(0, M_PI / 2.0, 0));
|
||
cut(groove_object, cut_matrix_right, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||
}
|
||
|
||
const Transform3d groove_translation = translation_transform(rotation_m * (offset_x * Vec3d::UnitX()));
|
||
// 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 = groove_translation * translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) *
|
||
m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||
|
||
cut(groove_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 = groove_translation * translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) *
|
||
m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||
|
||
cut(groove_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 = groove_translation * translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) *
|
||
m_cut_matrix;
|
||
cut(groove_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 = groove_translation * translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) *
|
||
m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
|
||
cut(groove_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||
|
||
const Transform3d cut_matrix_angle2_tolerance = groove_translation * translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) *
|
||
m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
|
||
cut(groove_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
|
||
}
|
||
|
||
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
|
||
|
||
groove_object->clear_volumes();
|
||
}
|
||
|
||
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, saved_paintings);
|
||
|
||
return m_model.objects;
|
||
}
|
||
|
||
float Cut::calculate_groove_width (const Cut::Groove& groove, const float m_radius)
|
||
{
|
||
// Compute same slot outer width used in preview plane
|
||
const double flap_width = is_approx(groove.flaps_angle, 0.f) ? groove.depth : groove.depth / sin(groove.flaps_angle);
|
||
const double total_flap_width = 2.0 * flap_width * cos(groove.flaps_angle);
|
||
const double slot_neck_half_width = 0.5f * (groove.width);
|
||
const double slot_mouth_half_width = 0.5 * (groove.width + total_flap_width);
|
||
const double plane_half_height = 0.5f* (1.5f * (1.5f *m_radius));
|
||
const double flap_taper_offset = plane_half_height * tan(groove.angle);
|
||
const double slot_outer_x_max = std::max(slot_mouth_half_width + flap_taper_offset, slot_neck_half_width + flap_taper_offset);
|
||
|
||
return float(2.0 * slot_outer_x_max);
|
||
}
|
||
|
||
} // namespace Slic3r
|
||
|