Cut: Initial porting of Cut Gizmo

This commit is contained in:
enricoturri1966 2023-10-31 23:01:05 +08:00 committed by Noisyfox
parent ce2836a7f9
commit 18406c31c0
34 changed files with 5638 additions and 1361 deletions

View file

@ -1553,23 +1553,11 @@ int CLI::run(int argc, char **argv)
o->cut(Z, m_config.opt_float("cut"), &out);
}
#else
ModelObject* object = model.objects.front();
const BoundingBoxf3& box = object->bounding_box();
const float Margin = 20.0;
const float max_x = box.size()(0) / 2.0 + Margin;
const float min_x = -max_x;
const float max_y = box.size()(1) / 2.0 + Margin;
const float min_y = -max_y;
std::array<Vec3d, 4> plane_points;
plane_points[0] = { min_x, min_y, 0 };
plane_points[1] = { max_x, min_y, 0 };
plane_points[2] = { max_x, max_y, 0 };
plane_points[3] = { min_x, max_y, 0 };
for (Vec3d& point : plane_points) {
point += box.center();
}
model.objects.front()->cut(0, plane_points, ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower);
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

@ -153,7 +153,9 @@ namespace ImGui
// const wchar_t CustomSupportsMarker = 0x1D;
// const wchar_t CustomSeamMarker = 0x1E;
// const wchar_t MmuSegmentationMarker = 0x1F;
const wchar_t PlugMarker = 0x1E;
const wchar_t DowelMarker = 0x1F;
const wchar_t SnapMarker = 0x20;
// Do not forget use following letters only in wstring
//BBS use 08xx to avoid unicode character which may be used
const wchar_t DocumentationButton = 0x0800;
@ -198,6 +200,7 @@ namespace ImGui
const wchar_t CloseBlockNotifHoverButton = 0x0834;
const wchar_t BlockNotifErrorIcon = 0x0835;
const wchar_t ClipboardBtnDarkIcon = 0x0836;
const wchar_t InfoMarkerSmall = 0x0837;
// void MyFunction(const char* name, const MyMatrix44& v);
}

View file

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

663
src/libslic3r/CutUtils.cpp Normal file
View file

@ -0,0 +1,663 @@
///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "CutUtils.hpp"
#include "Geometry.hpp"
#include "libslic3r.h"
#include "Model.hpp"
#include "TriangleMeshSlicer.hpp"
#include "TriangleSelector.hpp"
#include "ObjectID.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
using namespace Geometry;
static void apply_tolerance(ModelVolume* vol)
{
ModelVolume::CutInfo& cut_info = vol->cut_info;
assert(cut_info.is_connector);
if (!cut_info.is_processed)
return;
Vec3d sf = vol->get_scaling_factor();
// make a "hole" wider
sf[X] += double(cut_info.radius_tolerance);
sf[Y] += double(cut_info.radius_tolerance);
// make a "hole" dipper
sf[Z] += double(cut_info.height_tolerance);
vol->set_scaling_factor(sf);
// correct offset in respect to the new depth
Vec3d rot_norm = rotation_transform(vol->get_rotation()) * Vec3d::UnitZ();
if (rot_norm.norm() != 0.0)
rot_norm.normalize();
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
if (cut_info.connector_type == CutConnectorType::Plug ||
cut_info.connector_type == CutConnectorType::Snap)
z_offset -= 0.05; // add small Z offset to better preview
vol->set_offset(vol->get_offset() + rot_norm * z_offset);
}
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
{
if (mesh.empty())
return;
mesh.transform(cut_matrix);
ModelVolume* vol = object->add_volume(mesh);
vol->set_type(type);
vol->name = src_volume->name + suffix;
// Don't copy the config's ID.
vol->config.assign_config(src_volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != src_volume->config.id());
vol->set_material(src_volume->material_id(), *src_volume->material());
vol->cut_info = src_volume->cut_info;
}
static void process_volume_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
{
const auto volume_matrix = volume->get_matrix();
const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
// Transform the mesh by the combined transformation matrix.
// Flip the triangles in case the composite transformation is left handed.
TriangleMesh mesh(volume->mesh());
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
indexed_triangle_set upper_its, lower_its;
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper_mesh = TriangleMesh(upper_its);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
static void process_connector_cut( ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels)
{
assert(volume->cut_info.is_connector);
volume->cut_info.set_processed();
const auto volume_matrix = volume->get_matrix();
// ! Don't apply instance transformation for the conntectors.
// This transformation is already there
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
ModelVolume* vol = nullptr;
if (volume->cut_info.connector_type == CutConnectorType::Snap) {
TriangleMesh mesh = TriangleMesh(its_make_cylinder(1.0, 1.0, PI / 180.));
vol = upper->add_volume(std::move(mesh));
vol->set_transformation(volume->get_transformation());
vol->set_type(ModelVolumeType::NEGATIVE_VOLUME);
vol->cut_info = volume->cut_info;
vol->name = volume->name;
}
else
vol = upper->add_volume(*volume);
vol->set_transformation(volume_matrix);
apply_tolerance(vol);
}
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
ModelVolume* vol = lower->add_volume(*volume);
vol->set_transformation(volume_matrix);
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
vol->set_type(ModelVolumeType::MODEL_PART);
}
}
else {
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
ModelObject* dowel{ nullptr };
// Clone the object to duplicate instances, materials etc.
volume->get_object()->clone_for_cut(&dowel);
// add one more solid part same as connector if this connector is a dowel
ModelVolume* vol = dowel->add_volume(*volume);
vol->set_type(ModelVolumeType::MODEL_PART);
// But discard rotation and Z-offset for this volume
vol->set_rotation(Vec3d::Zero());
vol->set_offset(Z, 0.0);
dowels.push_back(dowel);
}
// Cut the dowel
apply_tolerance(volume);
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
// add small Z offset to better preview
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
// Add cut parts to the related objects
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
}
}
static void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
{
const auto volume_matrix = instance_matrix * volume->get_matrix();
// Modifiers are not cut, but we still need to add the instance transformation
// to the modifier volume transformation to preserve their shape properly.
volume->set_transformation(Transformation(volume_matrix));
if (attributes.has(ModelObjectCutAttribute::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");
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(false, false, true));
// 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)
{
//clear model from temporarry objects
m_model.clear_objects();
// add to model result objects
m_model.objects = objects;
}
const ModelObjectPtrs& Cut::perform_with_plane()
{
if (!m_attributes.has(ModelObjectCutAttribute::KeepUpper) && !m_attributes.has(ModelObjectCutAttribute::KeepLower)) {
m_model.clear_objects();
return m_model.objects;
}
ModelObject* mo = m_model.objects.front();
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper))
mo->clone_for_cut(&upper);
ModelObject* lower{ nullptr };
if (m_attributes.has(ModelObjectCutAttribute::KeepLower) && !m_attributes.has(ModelObjectCutAttribute::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(true);
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);
}
};
post_process(upper, lower, cut_object_ptrs);
delete_extra_modifiers(upper);
delete_extra_modifiers(lower);
if (m_attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
for (auto dowel : dowels) {
reset_instance_transformation(dowel, m_instance);
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
cut_object_ptrs.push_back(dowel);
}
}
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
finalize(cut_object_ptrs);
return m_model.objects;
}
static void distribute_modifiers_from_object(ModelObject* from_obj, const int instance_idx, ModelObject* to_obj1, ModelObject* to_obj2)
{
auto obj1_bb = to_obj1 ? to_obj1->instance_bounding_box(instance_idx) : BoundingBoxf3();
auto obj2_bb = to_obj2 ? to_obj2->instance_bounding_box(instance_idx) : BoundingBoxf3();
const Transform3d inst_matrix = from_obj->instances[instance_idx]->get_transformation().get_matrix();
for (ModelVolume* vol : from_obj->volumes)
if (!vol->is_model_part()) {
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
// Don't add modifiers which are not intersecting with solid parts
if (obj1_bb.intersects(bb))
to_obj1->add_volume(*vol);
if (obj2_bb.intersects(bb))
to_obj2->add_volume(*vol);
}
}
static void merge_solid_parts_inside_object(ModelObjectPtrs& objects)
{
for (ModelObject* mo : objects) {
TriangleMesh mesh;
// Merge all SolidPart but not Connectors
for (const ModelVolume* mv : mo->volumes) {
if (mv->is_model_part() && !mv->is_cut_connector()) {
TriangleMesh m = mv->mesh();
m.transform(mv->get_matrix());
mesh.merge(m);
}
}
if (!mesh.empty()) {
ModelVolume* new_volume = mo->add_volume(mesh);
new_volume->name = mo->name;
// Delete all merged SolidPart but not Connectors
for (int i = int(mo->volumes.size()) - 2; i >= 0; --i) {
const ModelVolume* mv = mo->volumes[i];
if (mv->is_model_part() && !mv->is_cut_connector())
mo->delete_volume(i);
}
}
}
}
const ModelObjectPtrs& Cut::perform_by_contour(std::vector<Part> parts, int dowels_count)
{
ModelObject* cut_mo = m_model.objects.front();
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
if (m_attributes.has(ModelObjectCutAttribute::KeepUpper)) cut_mo->clone_for_cut(&upper);
ModelObject* lower{ nullptr };
if (m_attributes.has(ModelObjectCutAttribute::KeepLower)) cut_mo->clone_for_cut(&lower);
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);
}
else if (volumes.size() > cut_parts_cnt) {
// Means that object is cut with connectors
// All volumes are distributed to Upper / Lower object,
// So we dont need them anymore
for (size_t id = 0; id < cut_parts_cnt; id++)
delete* (volumes.begin() + id);
volumes.erase(volumes.begin(), volumes.begin() + cut_parts_cnt);
// Perform cut just to get connectors
Cut cut(cut_mo, m_instance, m_cut_matrix, m_attributes);
const ModelObjectPtrs& cut_connectors_obj = cut.perform_with_plane();
assert(dowels_count > 0 ? cut_connectors_obj.size() >= 3 : cut_connectors_obj.size() == 2);
// Connectors from upper object
for (const ModelVolume* volume : cut_connectors_obj[0]->volumes)
upper->add_volume(*volume, volume->type());
// Connectors from lower object
for (const ModelVolume* volume : cut_connectors_obj[1]->volumes)
lower->add_volume(*volume, volume->type());
// Add Upper and Lower objects to cut_object_ptrs
post_process(upper, lower, cut_object_ptrs);
// Now merge all model parts together:
merge_solid_parts_inside_object(cut_object_ptrs);
// replace initial objects in model with cut object
finalize(cut_object_ptrs);
// 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, bool keep_as_parts/* = false*/)
{
ModelObject* cut_mo = m_model.objects.front();
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
cut_mo->clone_for_cut(&upper);
ModelObject* lower{ nullptr };
cut_mo->clone_for_cut(&lower);
const double groove_half_depth = 0.5 * double(groove.depth);
Model tmp_model_for_cut = Model();
Model tmp_model = Model();
tmp_model.add_object(*cut_mo);
ModelObject* tmp_object = tmp_model.objects.front();
auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) {
const auto& volumes = tmp_model_for_cut.objects.front()->volumes;
for (const ModelVolume* volume : volumes)
if (volume->is_model_part()) {
if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) ||
(attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) {
ModelVolume* new_vol = object->add_volume(*volume);
new_vol->reset_from_upper();
}
}
};
auto cut = [this, add_volumes_from_cut]
(ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) {
Cut cut(object, m_instance, cut_matrix);
tmp_model_for_cut = Model();
tmp_model_for_cut.add_object(*cut.perform_with_plane().front());
assert(!tmp_model_for_cut.objects.empty());
object->clear_volumes();
add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
reset_instance_transformation(object, m_instance);
};
// cut by upper plane
const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
{
cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// cut by lower plane
const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
{
cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
}
// cut middle part with 2 angles and add parts to related upper/lower objects
const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
// cut by angle1 plane
{
const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// cut by angle2 plane
{
const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// apply tolerance to the middle part
{
const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// this part can be added to the upper object now
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
ModelObjectPtrs cut_object_ptrs;
if (keep_as_parts) {
// add volumes from lower object to the upper, but mark them as a lower
const auto& volumes = lower->volumes;
for (const ModelVolume* volume : volumes) {
ModelVolume* new_vol = upper->add_volume(*volume);
new_vol->cut_info.is_from_upper = false;
}
// add modifiers
for (const ModelVolume* volume : cut_mo->volumes)
if (!volume->is_model_part())
upper->add_volume(*volume);
cut_object_ptrs.push_back(upper);
// add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks
cut_object_ptrs.push_back(lower);
}
else {
// add modifiers if object has any
for (const ModelVolume* volume : cut_mo->volumes)
if (!volume->is_model_part()) {
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
break;
}
assert(!upper->volumes.empty() && !lower->volumes.empty());
// Add Upper and Lower parts to cut_object_ptrs
post_process(upper, lower, cut_object_ptrs);
// Now merge all model parts together:
merge_solid_parts_inside_object(cut_object_ptrs);
}
finalize(cut_object_ptrs);
return m_model.objects;
}
ModelObjectPtrs Cut::cut_horizontal(const ModelObject *object, size_t instance_idx, double z, ModelObjectCutAttributes attributes)
{
Cut cut(object, instance_idx, Geometry::translation_transform(z * Vec3d::UnitZ()), attributes);
return cut.perform_with_plane();
}
} // namespace Slic3r

View file

@ -0,0 +1,74 @@
///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_CutUtils_hpp_
#define slic3r_CutUtils_hpp_
#include "enum_bitmask.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;
void post_process(ModelObject* object, ModelObjectPtrs& objects, bool keep, bool place_on_cut, bool flip);
void post_process(ModelObject* upper_object, ModelObject* lower_object, ModelObjectPtrs& objects);
void finalize(const ModelObjectPtrs& objects);
public:
Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes = ModelObjectCutAttribute::KeepUpper |
ModelObjectCutAttribute::KeepLower |
ModelObjectCutAttribute::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);
static ModelObjectPtrs cut_horizontal(const ModelObject *object, size_t instance_idx, double z, ModelObjectCutAttributes attributes);
}; // namespace Cut
} // namespace Slic3r
#endif /* slic3r_CutUtils_hpp_ */

View file

@ -1,3 +1,8 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros
///|/ Copyright (c) 2020 Henner Zeller
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "../libslic3r.h"
#include "../Exception.hpp"
#include "../Model.hpp"
@ -741,8 +746,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
{
int volume_id;
int type;
float radius;
float height;
float r_tolerance;
float h_tolerance;
};
@ -758,10 +761,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//typedef std::map<Id, ComponentsList> IdToAliasesMap;
typedef std::vector<Instance> InstancesList;
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
//typedef std::map<Id, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
typedef std::map<int, CutObjectInfo> IdToCutObjectInfoMap;
/*typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;*/
@ -1013,7 +1016,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
bool _extract_xml_from_archive(mz_zip_archive& archive, std::string const & path, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
bool _extract_xml_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, XML_StartElementHandler start_handler, XML_EndElementHandler end_handler);
bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions);
void _extract_cut_information_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions);
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
@ -1955,11 +1958,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
IdToCutObjectInfoMap::iterator cut_object_info = m_cut_object_infos.find(object.second + 1);
if (cut_object_info != m_cut_object_infos.end()) {
model_object->cut_id = cut_object_info->second.id;
int vol_cnt = int(model_object->volumes.size());
for (auto connector : cut_object_info->second.connectors) {
assert(0 <= connector.volume_id && connector.volume_id <= int(model_object->volumes.size()));
if (connector.volume_id < 0 || connector.volume_id >= vol_cnt) {
add_error("Invalid connector is found");
continue;
}
model_object->volumes[connector.volume_id]->cut_info =
ModelVolume::CutInfo(CutConnectorType(connector.type), connector.radius, connector.height, connector.r_tolerance, connector.h_tolerance, true);
ModelVolume::CutInfo(CutConnectorType(connector.type), connector.r_tolerance, connector.h_tolerance, true);
}
}
}
@ -2324,7 +2330,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
void _BBS_3MF_Importer::_extract_cut_information_from_archive(mz_zip_archive &archive, const mz_zip_archive_file_stat &stat, ConfigSubstitutionContext &config_substitutions)
{
if (stat.m_uncomp_size > 0) {
std::string buffer((size_t) stat.m_uncomp_size, 0);
std::string buffer((size_t)stat.m_uncomp_size, 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void *) buffer.data(), (size_t) stat.m_uncomp_size, 0);
if (res == 0) {
add_error("Error while reading cut information data to buffer");
@ -2335,7 +2341,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
pt::ptree objects_tree;
pt::read_xml(iss, objects_tree);
for (const auto &object : objects_tree.get_child("objects")) {
for (const auto& object : objects_tree.get_child("objects")) {
pt::ptree object_tree = object.second;
int obj_idx = object_tree.get<int>("<xmlattr>.id", -1);
if (obj_idx <= 0) {
@ -2352,27 +2358,30 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
CutObjectBase cut_id;
std::vector<CutObjectInfo::Connector> connectors;
for (const auto &obj_cut_info : object_tree) {
for (const auto& obj_cut_info : object_tree) {
if (obj_cut_info.first == "cut_id") {
pt::ptree cut_id_tree = obj_cut_info.second;
cut_id = CutObjectBase(ObjectID(cut_id_tree.get<size_t>("<xmlattr>.id")), cut_id_tree.get<size_t>("<xmlattr>.check_sum"),
cut_id = CutObjectBase(ObjectID( cut_id_tree.get<size_t>("<xmlattr>.id")),
cut_id_tree.get<size_t>("<xmlattr>.check_sum"),
cut_id_tree.get<size_t>("<xmlattr>.connectors_cnt"));
}
if (obj_cut_info.first == "connectors") {
pt::ptree cut_connectors_tree = obj_cut_info.second;
for (const auto &cut_connector : cut_connectors_tree) {
if (cut_connector.first != "connector") continue;
for (const auto& cut_connector : cut_connectors_tree) {
if (cut_connector.first != "connector")
continue;
pt::ptree connector_tree = cut_connector.second;
CutObjectInfo::Connector connector = {connector_tree.get<int>("<xmlattr>.volume_id"), connector_tree.get<int>("<xmlattr>.type"),
connector_tree.get<float>("<xmlattr>.radius", 0.f), connector_tree.get<float>("<xmlattr>.height", 0.f),
connector_tree.get<float>("<xmlattr>.r_tolerance"), connector_tree.get<float>("<xmlattr>.h_tolerance")};
CutObjectInfo::Connector connector = {connector_tree.get<int>("<xmlattr>.volume_id"),
connector_tree.get<int>("<xmlattr>.type"),
connector_tree.get<float>("<xmlattr>.r_tolerance"),
connector_tree.get<float>("<xmlattr>.h_tolerance")};
connectors.emplace_back(connector);
}
}
}
CutObjectInfo cut_info{cut_id, connectors};
m_cut_object_infos.insert({obj_idx, cut_info});
CutObjectInfo cut_info {cut_id, connectors};
m_cut_object_infos.insert({ obj_idx, cut_info });
}
}
}
@ -5260,6 +5269,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//BBS: change volume to seperate objects
bool _add_mesh_to_object_stream(std::function<bool(std::string &, bool)> const &flush, ObjectData const &object_data) const;
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const;
bool _add_cut_information_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
@ -5270,7 +5280,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
//BBS: add project embedded preset files
bool _add_project_embedded_presets_to_archive(mz_zip_archive& archive, Model& model, std::vector<Preset*> project_presets);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, int export_plate_idx = -1, bool save_gcode = true, bool use_loaded_id = false);
bool _add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model);
bool _add_slice_info_config_file_to_archive(mz_zip_archive &archive, const Model &model, PlateDataPtrs &plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config);
bool _add_gcode_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, Export3mfProgressFn proFn = nullptr);
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
@ -6704,6 +6713,69 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model)
{
std::string out = "";
pt::ptree tree;
unsigned int object_cnt = 0;
for (const ModelObject* object : model.objects) {
object_cnt++;
if (!object->is_cut())
continue;
pt::ptree& obj_tree = tree.add("objects.object", "");
obj_tree.put("<xmlattr>.id", object_cnt);
// Store info for cut_id
pt::ptree& cut_id_tree = obj_tree.add("cut_id", "");
// store cut_id atributes
cut_id_tree.put("<xmlattr>.id", object->cut_id.id().id);
cut_id_tree.put("<xmlattr>.check_sum", object->cut_id.check_sum());
cut_id_tree.put("<xmlattr>.connectors_cnt", object->cut_id.connectors_cnt());
int volume_idx = -1;
for (const ModelVolume* volume : object->volumes) {
++volume_idx;
if (volume->is_cut_connector()) {
pt::ptree& connectors_tree = obj_tree.add("connectors.connector", "");
connectors_tree.put("<xmlattr>.volume_id", volume_idx);
connectors_tree.put("<xmlattr>.type", int(volume->cut_info.connector_type));
connectors_tree.put("<xmlattr>.r_tolerance", volume->cut_info.radius_tolerance);
connectors_tree.put("<xmlattr>.h_tolerance", volume->cut_info.height_tolerance);
}
}
}
if (!tree.empty()) {
std::ostringstream oss;
pt::write_xml(oss, tree);
out = oss.str();
// Post processing("beautification") of the output string for a better preview
boost::replace_all(out, "><object", ">\n <object");
boost::replace_all(out, "><cut_id", ">\n <cut_id");
boost::replace_all(out, "></cut_id>", ">\n </cut_id>");
boost::replace_all(out, "><connectors", ">\n <connectors");
boost::replace_all(out, "></connectors>", ">\n </connectors>");
boost::replace_all(out, "><connector", ">\n <connector");
boost::replace_all(out, "></connector>", ">\n </connector>");
boost::replace_all(out, "></object>", ">\n </object>");
// OR just
boost::replace_all(out, "><", ">\n<");
}
if (!out.empty()) {
if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
add_error("Unable to add cut information file to archive");
return false;
}
}
return true;
}
bool _BBS_3MF_Exporter::_add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model)
{
assert(is_decimal_separator_point());
@ -7266,69 +7338,6 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
return true;
}
bool _BBS_3MF_Exporter::_add_cut_information_file_to_archive(mz_zip_archive &archive, Model &model)
{
std::string out = "";
pt::ptree tree;
unsigned int object_cnt = 0;
for (const ModelObject *object : model.objects) {
object_cnt++;
pt::ptree &obj_tree = tree.add("objects.object", "");
obj_tree.put("<xmlattr>.id", object_cnt);
// Store info for cut_id
pt::ptree &cut_id_tree = obj_tree.add("cut_id", "");
// store cut_id atributes
cut_id_tree.put("<xmlattr>.id", object->cut_id.id().id);
cut_id_tree.put("<xmlattr>.check_sum", object->cut_id.check_sum());
cut_id_tree.put("<xmlattr>.connectors_cnt", object->cut_id.connectors_cnt());
int volume_idx = -1;
for (const ModelVolume *volume : object->volumes) {
++volume_idx;
if (volume->is_cut_connector()) {
pt::ptree &connectors_tree = obj_tree.add("connectors.connector", "");
connectors_tree.put("<xmlattr>.volume_id", volume_idx);
connectors_tree.put("<xmlattr>.type", int(volume->cut_info.connector_type));
connectors_tree.put("<xmlattr>.radius", volume->cut_info.radius);
connectors_tree.put("<xmlattr>.height", volume->cut_info.height);
connectors_tree.put("<xmlattr>.r_tolerance", volume->cut_info.radius_tolerance);
connectors_tree.put("<xmlattr>.h_tolerance", volume->cut_info.height_tolerance);
}
}
}
if (!tree.empty()) {
std::ostringstream oss;
pt::write_xml(oss, tree);
out = oss.str();
// Post processing("beautification") of the output string for a better preview
boost::replace_all(out, "><object", ">\n <object");
boost::replace_all(out, "><cut_id", ">\n <cut_id");
boost::replace_all(out, "></cut_id>", ">\n </cut_id>");
boost::replace_all(out, "><connectors", ">\n <connectors");
boost::replace_all(out, "></connectors>", ">\n </connectors>");
boost::replace_all(out, "><connector", ">\n <connector");
boost::replace_all(out, "></connector>", ">\n </connector>");
boost::replace_all(out, "></object>", ">\n </object>");
// OR just
boost::replace_all(out, "><", ">\n<");
}
if (!out.empty()) {
if (!mz_zip_writer_add_mem(&archive, CUT_INFORMATION_FILE.c_str(), (const void *) out.data(), out.length(), MZ_DEFAULT_COMPRESSION)) {
add_error("Unable to add cut information file to archive");
return false;
}
}
return true;
}
bool _BBS_3MF_Exporter::_add_slice_info_config_file_to_archive(mz_zip_archive& archive, const Model& model, PlateDataPtrs& plate_data_list, const ObjectToObjectDataMap &objects_data, const DynamicPrintConfig& config)
{
std::stringstream stream;

View file

@ -1,3 +1,17 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, Tomáš Mészáros @tamasmeszaros
///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
///|/
///|/ ported from lib/Slic3r/Geometry.pm:
///|/ Copyright (c) Prusa Research 2017 - 2022 Vojtěch Bubník @bubnikv
///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2013 Jose Luis Perez Diez
///|/ Copyright (c) 2013 Anders Sundman
///|/ Copyright (c) 2013 Jesse Vincent
///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake
///|/ Copyright (c) 2012 Mark Hindess
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "libslic3r.h"
#include "Exception.hpp"
#include "Geometry.hpp"
@ -432,6 +446,14 @@ Vec3d extract_euler_angles(const Transform3d& transform)
return extract_euler_angles(m);
}
static Transform3d extract_rotation_matrix(const Transform3d& trafo)
{
Matrix3d rotation;
Matrix3d scale;
trafo.computeRotationScaling(&rotation, &scale);
return Transform3d(rotation);
}
void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix)
{
double epsilon = 1e-5;
@ -504,6 +526,11 @@ void Transformation::set_offset(Axis axis, double offset)
}
}
Transform3d Transformation::get_rotation_matrix() const
{
return extract_rotation_matrix(m_matrix);
}
void Transformation::set_rotation(const Vec3d& rotation)
{
set_rotation(X, rotation.x());

View file

@ -440,6 +440,8 @@ public:
const Vec3d& get_rotation() const { return m_rotation; }
double get_rotation(Axis axis) const { return m_rotation(axis); }
Transform3d get_rotation_matrix() const;
void set_rotation(const Vec3d& rotation);
void set_rotation(Axis axis, double rotation);

View file

@ -1,3 +1,16 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas, Filip Sykala @Jony01, Vojtěch Král @vojtechkral
///|/ Copyright (c) 2021 Boleslaw Ciesielski
///|/ Copyright (c) 2019 John Drake @foxox
///|/ Copyright (c) 2019 Sijmen Schoon
///|/ Copyright (c) Slic3r 2014 - 2016 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard
///|/
///|/ ported from lib/Slic3r/Model.pm:
///|/ Copyright (c) Prusa Research 2016 - 2022 Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966
///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Model.hpp"
#include "libslic3r.h"
#include "BuildVolume.hpp"
@ -1693,68 +1706,6 @@ bool ModelObject::has_connectors() const
return false;
}
indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes)
{
indexed_triangle_set connector_mesh;
int sectorCount {1};
switch (CutConnectorShape(connector_attributes.shape)) {
case CutConnectorShape::Triangle:
sectorCount = 3;
break;
case CutConnectorShape::Square:
sectorCount = 4;
break;
case CutConnectorShape::Circle:
sectorCount = 360;
break;
case CutConnectorShape::Hexagon:
sectorCount = 6;
break;
default:
break;
}
if (connector_attributes.style == CutConnectorStyle::Prizm)
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_cone(1.0, 1.0, (2 * PI / sectorCount));
else
connector_mesh = its_make_frustum_dowel(1.0, 1.0, sectorCount);
return connector_mesh;
}
void ModelObject::apply_cut_connectors(const std::string &name)
{
if (cut_connectors.empty())
return;
using namespace Geometry;
size_t connector_id = cut_id.connectors_cnt();
for (const CutConnector &connector : cut_connectors) {
TriangleMesh mesh = TriangleMesh(get_connector_mesh(connector.attribs));
// Mesh will be centered when loading.
ModelVolume *new_volume = add_volume(std::move(mesh), ModelVolumeType::NEGATIVE_VOLUME);
Transform3d translate_transform = Transform3d::Identity();
translate_transform.translate(connector.pos);
Transform3d scale_transform = Transform3d::Identity();
scale_transform.scale(Vec3f(connector.radius, connector.radius, connector.height).cast<double>());
// Transform the new modifier to be aligned inside the instance
new_volume->set_transformation(translate_transform * connector.rotation_m * scale_transform);
new_volume->cut_info = {connector.attribs.type, connector.radius, connector.height, connector.radius_tolerance, connector.height_tolerance};
new_volume->name = name + "-" + std::to_string(++connector_id);
}
cut_id.increase_connectors_cnt(cut_connectors.size());
// delete all connectors
cut_connectors.clear();
}
void ModelObject::invalidate_cut()
{
this->cut_id.invalidate();
@ -1770,43 +1721,10 @@ void ModelObject::delete_connectors()
}
}
void ModelObject::synchronize_model_after_cut()
{
for (ModelObject *obj : m_model->objects) {
if (obj == this || obj->cut_id.is_equal(this->cut_id)) continue;
if (obj->is_cut() && obj->cut_id.has_same_id(this->cut_id))
obj->cut_id.copy(this->cut_id);
}
}
void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
{
// we don't save cut information, if result will not contains all parts of initial object
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) ||
!attributes.has(ModelObjectCutAttribute::KeepLower) ||
attributes.has(ModelObjectCutAttribute::InvalidateCutInfo))
return;
if (cut_id.id().invalid())
cut_id.init();
{
int cut_obj_cnt = -1;
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
cut_obj_cnt++;
if (attributes.has(ModelObjectCutAttribute::KeepLower))
cut_obj_cnt++;
if (attributes.has(ModelObjectCutAttribute::CreateDowels))
cut_obj_cnt++;
if (cut_obj_cnt > 0)
cut_id.increase_check_sum(size_t(cut_obj_cnt));
}
}
void ModelObject::clone_for_cut(ModelObject **obj)
{
(*obj) = ModelObject::new_clone(*this);
(*obj)->set_model(nullptr);
(*obj)->set_model(this->get_model());
(*obj)->sla_support_points.clear();
(*obj)->sla_drain_holes.clear();
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;
@ -1814,189 +1732,11 @@ void ModelObject::clone_for_cut(ModelObject **obj)
(*obj)->input_file.clear();
}
Transform3d ModelObject::calculate_cut_plane_inverse_matrix(const std::array<Vec3d, 4>& plane_points)
void ModelVolume::reset_extra_facets()
{
Vec3d mid_point = {0.0, 0.0, 0.0};
for (auto pt : plane_points)
mid_point += pt;
mid_point /= (double) plane_points.size();
Vec3d movement = -mid_point;
Vec3d v01 = plane_points[1] - plane_points[0];
Vec3d v12 = plane_points[2] - plane_points[1];
Vec3d plane_normal = v01.cross(v12);
plane_normal.normalize();
Vec3d axis = {0.0, 0.0, 0.0};
double phi = 0.0;
Matrix3d matrix;
matrix.setIdentity();
Geometry::rotation_from_two_vectors(plane_normal, {0.0, 0.0, 1.0}, axis, phi, &matrix);
Vec3d angles = Geometry::extract_euler_angles(matrix);
movement = matrix * movement;
Transform3d transfo;
transfo.setIdentity();
transfo.translate(movement);
transfo.rotate(Eigen::AngleAxisd(angles(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(angles(1), Vec3d::UnitY()) * Eigen::AngleAxisd(angles(0), Vec3d::UnitX()));
return transfo;
}
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,
Vec3d &local_dowels_displace)
{
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 = 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);
// Compute the displacement (in instance coordinates) to be applied to place the dowels
local_dowels_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(1.0, 1.0, 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::CutToParts)) {
upper->add_volume(*volume);
return;
}
// Some logic for the negative volumes/connectors. Add only needed modifiers
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
upper->add_volume(*volume);
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
lower->add_volume(*volume);
}
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 Geometry::Transformation cut_transformation = Geometry::Transformation(cut_matrix);
const Transform3d invert_cut_matrix = cut_transformation.get_matrix(true, false, true, true).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,
const std::array<Vec3d, 4> &plane_points,
ModelObjectCutAttributes attributes,
ModelObject * upper,
ModelObject * lower,
Vec3d & local_displace)
{
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
// Add required cut parts to the objects
if (attributes.has(ModelObjectCutAttribute::CutToParts)) {
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
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);
// Compute the displacement (in instance coordinates) to be applied to place the upper parts
// The upper part displacement is set to half of the lower part bounding box
// this is done in hope at least a part of the upper part will always be visible and draggable
local_displace = lower->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
}
this->supported_facets.reset();
this->seam_facets.reset();
this->mmu_segmentation_facets.reset();
}
static void invalidate_translations(ModelObject* object, const ModelInstance* src_instance)
@ -2073,215 +1813,6 @@ static void reset_instance_transformation(ModelObject* object, size_t src_instan
}
}
// BBS: replace z with plane_points
ModelObjectPtrs ModelObject::cut(size_t instance, std::array<Vec3d, 4> plane_points, ModelObjectCutAttributes attributes)
{
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
return {};
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
// apply cut attributes for object
apply_cut_attributes(attributes);
ModelObject* upper{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
clone_for_cut(&upper);
ModelObject* lower{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::CutToParts))
clone_for_cut(&lower);
// 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 = Geometry::assemble_transform(
Vec3d::Zero(), // don't apply offset
instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 1.0)), // BBS: do apply Z-rotation
instances[instance]->get_scaling_factor(),
instances[instance]->get_mirror()
);
// BBS
//z -= instances[instance]->get_offset().z();
for (Vec3d& point : plane_points) {
point -= instances[instance]->get_offset();
}
Transform3d inverse_cut_matrix = calculate_cut_plane_inverse_matrix(plane_points);
Transform3d cut_matrix = inverse_cut_matrix.inverse();
std::vector<ModelObject *> dowels;
// Displacement (in instance coordinates) to be applied to place the upper parts
Vec3d local_displace = Vec3d::Zero();
Vec3d local_dowels_displace = Vec3d::Zero();
for (ModelVolume *volume : volumes) {
const auto volume_matrix = volume->get_matrix();
volume->supported_facets.reset();
volume->seam_facets.reset();
volume->mmu_segmentation_facets.reset();
if (! volume->is_model_part()) {
if (volume->cut_info.is_processed) {
// Modifiers are not cut, but we still need to add the instance transformation
// to the modifier volume transformation to preserve their shape properly.
//Transform3d inverse_cut_matrix = calculate_cut_plane_inverse_matrix(plane_points);
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, local_dowels_displace);
}
}
else if (! volume->mesh().empty()) {
process_solid_part_cut(volume, instance_matrix, cut_matrix, plane_points, attributes, upper, lower, local_displace);
}
}
ModelObjectPtrs res;
if (attributes.has(ModelObjectCutAttribute::CutToParts) && !upper->volumes.empty()) {
reset_instance_transformation(upper, instance, cut_matrix);
res.push_back(upper);
}
else {
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) {
reset_instance_transformation(upper, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
attributes.has(ModelObjectCutAttribute::FlipUpper), local_displace);
res.push_back(upper);
}
if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) {
reset_instance_transformation(lower, instance, cut_matrix, attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) ? true : 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(), false, false, local_dowels_displace);
local_dowels_displace += dowel->full_raw_mesh_bounding_box().size().cwiseProduct(Vec3d(-1.5, -1.5, 0.0));
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
res.push_back(dowel);
}
}
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
synchronize_model_after_cut();
return res;
}
// BBS
ModelObjectPtrs ModelObject::segment(size_t instance, unsigned int max_extruders, double smoothing_alpha, int segment_number)
{
BOOST_LOG_TRIVIAL(trace) << "ModelObject::segment - start";
// Clone the object to duplicate instances, materials etc.
ModelObject* upper = ModelObject::new_clone(*this);
upper->set_model(nullptr);
upper->sla_support_points.clear();
upper->sla_drain_holes.clear();
upper->sla_points_status = sla::PointsStatus::NoPoints;
upper->clear_volumes();
upper->input_file.clear();
// 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 = Geometry::assemble_transform(
Vec3d::Zero(), // don't apply offset
instances[instance]->get_rotation(), // BBS: keep Z-rotation
instances[instance]->get_scaling_factor(),
instances[instance]->get_mirror()
);
for (ModelVolume* volume : volumes) {
const auto volume_matrix = volume->get_matrix();
volume->supported_facets.reset();
volume->seam_facets.reset();
if (!volume->is_model_part()) {
// 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(instance_matrix * volume_matrix));
upper->add_volume(*volume);
}
else if (!volume->mesh().empty()) {
// 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(instance_matrix * volume_matrix, true);
volume->reset_mesh();
auto mesh_segments = MeshBoolean::cgal::segment(mesh, smoothing_alpha, segment_number);
// Reset volume transformation except for offset
const Vec3d offset = volume->get_offset();
volume->set_transformation(Geometry::Transformation());
volume->set_offset(offset);
unsigned int extruder_counter = 0;
for (int idx=0;idx<mesh_segments.size();idx++)
{
auto& mesh_segment = mesh_segments[idx];
if (mesh_segment.facets_count() > 0) {
ModelVolume* vol = upper->add_volume(mesh_segment);
vol->name = volume->name.substr(0, volume->name.find_last_of('.')) + "_" + std::to_string(idx);
// Don't copy the config's ID.
vol->config.assign_config(volume->config);
#if 0
assert(vol->config.id().valid());
assert(vol->config.id() != volume->config.id());
vol->set_material(volume->material_id(), *volume->material());
#else
vol->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
#endif
}
}
}
}
ModelObjectPtrs res;
if (upper->volumes.size() > 0) {
upper->invalidate_bounding_box();
// Reset instance transformation except offset and Z-rotation
for (size_t i = 0; i < instances.size(); i++) {
auto& instance = upper->instances[i];
const Vec3d offset = instance->get_offset();
// BBS
//const double rot_z = instance->get_rotation()(2);
instance->set_transformation(Geometry::Transformation());
instance->set_offset(offset);
// BBS
//instance->set_rotation(Vec3d(0.0, 0.0, rot_z));
}
res.push_back(upper);
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::segment - end";
return res;
}
void ModelObject::split(ModelObjectPtrs* new_objects)
{
std::vector<TriangleMesh> all_meshes;
@ -2801,35 +2332,6 @@ bool ModelVolume::is_splittable() const
return m_is_splittable == 1;
}
void ModelVolume::apply_tolerance()
{
assert(cut_info.is_connector);
if (!cut_info.is_processed)
return;
Vec3d sf = get_scaling_factor();
// make a "hole" wider
double size_scale = 1.f;
if (abs(cut_info.radius - 0) < EPSILON) // For compatibility with old files
size_scale = 1.f + double(cut_info.radius_tolerance);
else
size_scale = (double(cut_info.radius) + double(cut_info.radius_tolerance)) / double(cut_info.radius);
sf[X] *= size_scale;
sf[Y] *= size_scale;
// make a "hole" dipper
double height_scale = 1.f;
if (abs(cut_info.height - 0) < EPSILON) // For compatibility with old files
height_scale = 1.f + double(cut_info.height_tolerance);
else
height_scale = (double(cut_info.height) + double(cut_info.height_tolerance)) / double(cut_info.height);
sf[Z] *= height_scale;
set_scaling_factor(sf);
}
// BBS
std::vector<int> ModelVolume::get_extruders() const
{

View file

@ -246,50 +246,56 @@ private:
};
enum class CutConnectorType : int {
Plug,
Dowel,
Undef
Plug
, Dowel
, Snap
, Undef
};
enum class CutConnectorStyle : int {
Prizm,
Frustum,
Undef
Prism
, Frustum
, Undef
//,Claw
};
enum class CutConnectorShape : int {
Triangle,
Square,
Hexagon,
Circle,
Undef
Triangle
, Square
, Hexagon
, Circle
, Undef
//,D-shape
};
struct CutConnectorAttributes
{
CutConnectorType type{CutConnectorType::Plug};
CutConnectorStyle style{CutConnectorStyle::Prizm};
CutConnectorShape shape{CutConnectorShape::Circle};
CutConnectorType type{ CutConnectorType::Plug };
CutConnectorStyle style{ CutConnectorStyle::Prism };
CutConnectorShape shape{ CutConnectorShape::Circle };
CutConnectorAttributes() {}
CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh) : type(t), style(st), shape(sh) {}
CutConnectorAttributes(CutConnectorType t, CutConnectorStyle st, CutConnectorShape sh)
: type(t), style(st), shape(sh)
{}
CutConnectorAttributes(const CutConnectorAttributes &rhs) : CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {}
CutConnectorAttributes(const CutConnectorAttributes& rhs) :
CutConnectorAttributes(rhs.type, rhs.style, rhs.shape) {}
bool operator==(const CutConnectorAttributes &other) const;
bool operator==(const CutConnectorAttributes& other) const;
bool operator!=(const CutConnectorAttributes &other) const { return !(other == (*this)); }
bool operator!=(const CutConnectorAttributes& other) const { return !(other == (*this)); }
bool operator<(const CutConnectorAttributes &other) const
{
return this->type < other.type || (this->type == other.type && this->style < other.style) ||
bool operator<(const CutConnectorAttributes& other) const {
return this->type < other.type ||
(this->type == other.type && this->style < other.style) ||
(this->type == other.type && this->style == other.style && this->shape < other.shape);
}
template<class Archive> inline void serialize(Archive &ar) { ar(type, style, shape); }
template<class Archive> inline void serialize(Archive& ar) {
ar(type, style, shape);
}
};
struct CutConnector
@ -298,27 +304,34 @@ struct CutConnector
Transform3d rotation_m;
float radius;
float height;
float radius_tolerance; // [0.f : 1.f]
float height_tolerance; // [0.f : 1.f]
float radius_tolerance;// [0.f : 1.f]
float height_tolerance;// [0.f : 1.f]
float z_angle {0.f};
CutConnectorAttributes attribs;
CutConnector() : pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f) {}
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, CutConnectorAttributes attributes)
: pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), attribs(attributes)
CutConnector()
: pos(Vec3d::Zero()), rotation_m(Transform3d::Identity()), radius(5.f), height(10.f), radius_tolerance(0.f), height_tolerance(0.1f), z_angle(0.f)
{}
CutConnector(const CutConnector &rhs) : CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.attribs) {}
CutConnector(Vec3d p, Transform3d rot, float r, float h, float rt, float ht, float za, CutConnectorAttributes attributes)
: pos(p), rotation_m(rot), radius(r), height(h), radius_tolerance(rt), height_tolerance(ht), z_angle(za), attribs(attributes)
{}
bool operator==(const CutConnector &other) const;
CutConnector(const CutConnector& rhs) :
CutConnector(rhs.pos, rhs.rotation_m, rhs.radius, rhs.height, rhs.radius_tolerance, rhs.height_tolerance, rhs.z_angle, rhs.attribs) {}
bool operator!=(const CutConnector &other) const { return !(other == (*this)); }
bool operator==(const CutConnector& other) const;
template<class Archive> inline void serialize(Archive &ar) { ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, attribs); }
bool operator!=(const CutConnector& other) const { return !(other == (*this)); }
template<class Archive> inline void serialize(Archive& ar) {
ar(pos, rotation_m, radius, height, radius_tolerance, height_tolerance, z_angle, attribs);
}
};
using CutConnectors = std::vector<CutConnector>;
// Declared outside of ModelVolume, so it could be forward declared.
enum class ModelVolumeType : int {
INVALID = -1,
@ -326,13 +339,9 @@ enum class ModelVolumeType : int {
NEGATIVE_VOLUME,
PARAMETER_MODIFIER,
SUPPORT_BLOCKER,
SUPPORT_ENFORCER
SUPPORT_ENFORCER,
};
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, CutToParts, 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,
@ -371,6 +380,10 @@ public:
// Holes to be drilled into the object so resin can flow out
sla::DrainHoles sla_drain_holes;
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
CutConnectors cut_connectors;
CutObjectBase cut_id;
/* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation
to new volumes before adding them to this object in order to preserve alignment
@ -380,10 +393,6 @@ public:
// BBS: save for compare with new load volumes
std::vector<ObjectID> volume_ids;
// Connectors to be added into the object before cut and are used to create a solid/negative volumes during a cut perform
CutConnectors cut_connectors;
CutObjectBase cut_id;
Model* get_model() { return m_model; }
const Model* get_model() const { return m_model; }
// BBS: production extension
@ -480,52 +489,13 @@ public:
size_t materials_count() const;
size_t facets_count() const;
size_t parts_count() const;
bool is_cut() const { return cut_id.id().valid(); }
bool has_connectors() const;
static indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
void apply_cut_connectors(const std::string &name);
// invalidate cut state for this object and its connectors/volumes
void invalidate_cut();
// delete volumes which are marked as connector for this object
void delete_connectors();
void synchronize_model_after_cut();
void apply_cut_attributes(ModelObjectCutAttributes attributes);
void clone_for_cut(ModelObject **obj);
Transform3d calculate_cut_plane_inverse_matrix(const std::array<Vec3d, 4> &plane_points);
void process_connector_cut(ModelVolume *volume,
const Transform3d & instance_matrix,
const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes,
ModelObject *upper, ModelObject *lower,
std::vector<ModelObject *> &dowels,
Vec3d &local_dowels_displace);
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,
const std::array<Vec3d, 4> &plane_points,
ModelObjectCutAttributes attributes,
ModelObject * upper,
ModelObject * lower,
Vec3d & local_displace);
// BBS: replace z with plane_points
ModelObjectPtrs cut(size_t instance, std::array<Vec3d, 4> plane_points, ModelObjectCutAttributes attributes);
// BBS
ModelObjectPtrs segment(size_t instance, unsigned int max_extruders, double smoothing_alpha = 0.5, int segment_number = 5);
void split(ModelObjectPtrs* new_objects);
void split(ModelObjectPtrs*new_objects);
void merge();
// BBS: Boolean opts - Musang King
@ -553,6 +523,8 @@ public:
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
int get_repaired_errors_count(const int vol_idx = -1) const;
bool is_cut() const { return cut_id.id().valid(); }
bool has_connectors() const;
private:
friend class Model;
// This constructor assigns new ID to this ModelObject and its config.
@ -857,27 +829,35 @@ public:
// It contains information about connetors
struct CutInfo
{
bool is_connector{false};
bool is_processed{true};
CutConnectorType connector_type{CutConnectorType::Plug};
float radius{0.f};
float height{0.f};
float radius_tolerance{0.f}; // [0.f : 1.f]
float height_tolerance{0.f}; // [0.f : 1.f]
bool is_from_upper{ true };
bool is_connector{ false };
bool is_processed{ true };
CutConnectorType connector_type{ CutConnectorType::Plug };
float radius_tolerance{ 0.f };// [0.f : 1.f]
float height_tolerance{ 0.f };// [0.f : 1.f]
CutInfo() = default;
CutInfo(CutConnectorType type, float radius_, float height_, float rad_tolerance, float h_tolerance, bool processed = false)
: is_connector(true), is_processed(processed), connector_type(type)
, radius(radius_), height(height_), radius_tolerance(rad_tolerance), height_tolerance(h_tolerance)
CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance, bool processed = false) :
is_connector(true),
is_processed(processed),
connector_type(type),
radius_tolerance(rad_tolerance),
height_tolerance(h_tolerance)
{}
void set_processed() { is_processed = true; }
void invalidate() { is_connector = false; }
void reset_from_upper() { is_from_upper = true; }
template<class Archive> inline void serialize(Archive &ar) { ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance); }
template<class Archive> inline void serialize(Archive& ar) {
ar(is_connector, is_processed, connector_type, radius_tolerance, height_tolerance);
}
};
CutInfo cut_info;
bool is_from_upper() const { return cut_info.is_from_upper; }
void reset_from_upper() { cut_info.reset_from_upper(); }
bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; }
void invalidate_cut_info() { cut_info.invalidate(); }
@ -922,6 +902,7 @@ public:
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
t_model_material_id material_id() const { return m_material_id; }
void reset_extra_facets();
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);
@ -931,8 +912,6 @@ public:
bool is_splittable() const;
void apply_tolerance();
// BBS
std::vector<int> get_extruders() const;
void update_extruder_count(size_t extruder_count);

View file

@ -1,3 +1,18 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Filip Sykala @Jony01, Lukáš Hejl @hejllukas, Vojtěch Král @vojtechkral
///|/ Copyright (c) 2019 Jason Tibbitts @jasontibbitts
///|/ Copyright (c) 2019 Sijmen Schoon
///|/ Copyright (c) 2016 Joseph Lenox @lordofhyphens
///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2015 Maksim Derbasov @ntfshard
///|/ Copyright (c) 2014 Miro Hrončok @hroncok
///|/ Copyright (c) 2014 Petr Ledvina @ledvinap
///|/
///|/ ported from lib/Slic3r/TriangleMesh.pm:
///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2012 - 2013 Mark Hindess
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Exception.hpp"
#include "TriangleMesh.hpp"
#include "TriangleMeshSlicer.hpp"
@ -958,6 +973,51 @@ indexed_triangle_set its_make_cylinder(double r, double h, double fa)
return mesh;
}
indexed_triangle_set its_make_frustum(double r, double h, double fa)
{
indexed_triangle_set mesh;
size_t n_steps = (size_t)ceil(2. * PI / fa);
double angle_step = 2. * PI / n_steps;
auto &vertices = mesh.vertices;
auto &facets = mesh.indices;
vertices.reserve(2 * n_steps + 2);
facets.reserve(4 * n_steps);
// 2 special vertices, top and bottom center, rest are relative to this
vertices.emplace_back(Vec3f(0.f, 0.f, 0.f));
vertices.emplace_back(Vec3f(0.f, 0.f, float(h)));
// for each line along the polygon approximating the top/bottom of the
// circle, generate four points and four facets (2 for the wall, 2 for the
// top and bottom.
// Special case: Last line shares 2 vertices with the first line.
Vec2f vec_top = Eigen::Rotation2Df(0.f) * Eigen::Vector2f(0, 0.5f*r);
Vec2f vec_botton = Eigen::Rotation2Df(0.f) * Eigen::Vector2f(0, r);
vertices.emplace_back(Vec3f(vec_botton(0), vec_botton(1), 0.f));
vertices.emplace_back(Vec3f(vec_top(0), vec_top(1), float(h)));
for (size_t i = 1; i < n_steps; ++i) {
vec_top = Eigen::Rotation2Df(angle_step * i) * Eigen::Vector2f(0, 0.5f*float(r));
vec_botton = Eigen::Rotation2Df(angle_step * i) * Eigen::Vector2f(0, float(r));
vertices.emplace_back(Vec3f(vec_botton(0), vec_botton(1), 0.f));
vertices.emplace_back(Vec3f(vec_top(0), vec_top(1), float(h)));
int id = (int)vertices.size() - 1;
facets.emplace_back( 0, id - 1, id - 3); // top
facets.emplace_back(id, 1, id - 2); // bottom
facets.emplace_back(id, id - 2, id - 3); // upper-right of side
facets.emplace_back(id, id - 3, id - 1); // bottom-left of side
}
// Connect the last set of vertices with the first.
int id = (int)vertices.size() - 1;
facets.emplace_back( 0, 2, id - 1);
facets.emplace_back( 3, 1, id);
facets.emplace_back(id, 2, 3);
facets.emplace_back(id, id - 1, 2);
return mesh;
}
indexed_triangle_set its_make_cone(double r, double h, double fa)
{
indexed_triangle_set mesh;
@ -984,61 +1044,6 @@ indexed_triangle_set its_make_cone(double r, double h, double fa)
return mesh;
}
// Generates mesh for a frustum dowel centered about the origin, using the count of sectors
// Note: This function uses code for sphere generation, but for stackCount = 2;
indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount)
{
int stackCount = 2;
float sectorStep = float(2. * M_PI / sectorCount);
float stackStep = float(M_PI / stackCount);
indexed_triangle_set mesh;
auto& vertices = mesh.vertices;
vertices.reserve((stackCount - 1) * sectorCount + 2);
for (int i = 0; i <= stackCount; ++i) {
// from pi/2 to -pi/2
double stackAngle = 0.5 * M_PI - stackStep * i;
double xy = radius * cos(stackAngle);
double z = radius * sin(stackAngle);
if (i == 0 || i == stackCount)
vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle))));
else
for (int j = 0; j < sectorCount; ++j) {
// from 0 to 2pi
double sectorAngle = sectorStep * j + 0.25 * M_PI;
vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast<float>());
}
}
auto& facets = mesh.indices;
facets.reserve(2 * (stackCount - 1) * sectorCount);
for (int i = 0; i < stackCount; ++i) {
// Beginning of current stack.
int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount);
int k1_first = k1;
// Beginning of next stack.
int k2 = (i == 0) ? 1 : (k1 + sectorCount);
int k2_first = k2;
for (int j = 0; j < sectorCount; ++j) {
// 2 triangles per sector excluding first and last stacks
int k1_next = k1;
int k2_next = k2;
if (i != 0) {
k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1);
facets.emplace_back(k1, k2, k1_next);
}
if (i + 1 != stackCount) {
k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1);
facets.emplace_back(k1_next, k2, k2_next);
}
k1 = k1_next;
k2 = k2_next;
}
}
return mesh;
}
indexed_triangle_set its_make_pyramid(float base, float height)
{
float a = base / 2.f;
@ -1116,6 +1121,182 @@ indexed_triangle_set its_make_sphere(double radius, double fa)
return mesh;
}
// Generates mesh for a frustum dowel centered about the origin, using the count of sectors
// Note: This function uses code for sphere generation, but for stackCount = 2;
indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorCount)
{
int stackCount = 2;
float sectorStep = float(2. * M_PI / sectorCount);
float stackStep = float(M_PI / stackCount);
indexed_triangle_set mesh;
auto& vertices = mesh.vertices;
vertices.reserve((stackCount - 1) * sectorCount + 2);
for (int i = 0; i <= stackCount; ++i) {
// from pi/2 to -pi/2
double stackAngle = 0.5 * M_PI - stackStep * i;
double xy = radius * cos(stackAngle);
double z = radius * sin(stackAngle);
if (i == 0 || i == stackCount)
vertices.emplace_back(Vec3f(float(xy), 0.f, float(h * sin(stackAngle))));
else
for (int j = 0; j < sectorCount; ++j) {
// from 0 to 2pi
double sectorAngle = sectorStep * j + 0.25 * M_PI;
vertices.emplace_back(Vec3d(xy * std::cos(sectorAngle), xy * std::sin(sectorAngle), z).cast<float>());
}
}
auto& facets = mesh.indices;
facets.reserve(2 * (stackCount - 1) * sectorCount);
for (int i = 0; i < stackCount; ++i) {
// Beginning of current stack.
int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount);
int k1_first = k1;
// Beginning of next stack.
int k2 = (i == 0) ? 1 : (k1 + sectorCount);
int k2_first = k2;
for (int j = 0; j < sectorCount; ++j) {
// 2 triangles per sector excluding first and last stacks
int k1_next = k1;
int k2_next = k2;
if (i != 0) {
k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1);
facets.emplace_back(k1, k2, k1_next);
}
if (i + 1 != stackCount) {
k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1);
facets.emplace_back(k1_next, k2, k2_next);
}
k1 = k1_next;
k2 = k2_next;
}
}
return mesh;
}
indexed_triangle_set its_make_snap(double r, double h, float space_proportion, float bulge_proportion)
{
const float radius = (float)r;
const float height = (float)h;
const size_t sectors_cnt = 10; //(float)fa;
const float halfPI = 0.5f * (float)PI;
const float space_len = space_proportion * radius;
const float b_len = radius;
const float m_len = (1 + bulge_proportion) * radius;
const float t_len = 0.5f * radius;
const float b_height = 0.f;
const float m_height = 0.5f * height;
const float t_height = height;
const float b_angle = acos(space_len/b_len);
const float t_angle = acos(space_len/t_len);
const float b_angle_step = b_angle / (float)sectors_cnt;
const float t_angle_step = t_angle / (float)sectors_cnt;
const Vec2f b_vec = Eigen::Vector2f(0, b_len);
const Vec2f t_vec = Eigen::Vector2f(0, t_len);
auto add_side_vertices = [b_vec, t_vec, b_height, m_height, t_height](std::vector<stl_vertex>& vertices, float b_angle, float t_angle, const Vec2f& m_vec) {
Vec2f b_pt = Eigen::Rotation2Df(b_angle) * b_vec;
Vec2f m_pt = Eigen::Rotation2Df(b_angle) * m_vec;
Vec2f t_pt = Eigen::Rotation2Df(t_angle) * t_vec;
vertices.emplace_back(Vec3f(b_pt(0), b_pt(1), b_height));
vertices.emplace_back(Vec3f(m_pt(0), m_pt(1), m_height));
vertices.emplace_back(Vec3f(t_pt(0), t_pt(1), t_height));
};
auto add_side_facets = [](std::vector<stl_triangle_vertex_indices>& facets, int vertices_cnt, int frst_id, int scnd_id) {
int id = vertices_cnt - 1;
facets.emplace_back(frst_id, id - 2, id - 5);
facets.emplace_back(id - 2, id - 1, id - 5);
facets.emplace_back(id - 1, id - 4, id - 5);
facets.emplace_back(id - 4, id - 1, id);
facets.emplace_back(id, id - 3, id - 4);
facets.emplace_back(id, scnd_id, id - 3);
};
const float f = (b_len - m_len) / m_len; // Flattening
auto get_m_len = [b_len, f](float angle) {
const float rad_sqr = b_len * b_len;
const float sin_sqr = sin(angle) * sin(angle);
const float f_sqr = (1-f)*(1-f);
return sqrtf(rad_sqr / (1 + (1 / f_sqr - 1) * sin_sqr));
};
auto add_sub_mesh = [add_side_vertices, add_side_facets, get_m_len,
b_height, t_height, b_angle, t_angle, b_angle_step, t_angle_step]
(indexed_triangle_set& mesh, float center_x, float angle_rotation, int frst_vertex_id) {
auto& vertices = mesh.vertices;
auto& facets = mesh.indices;
// 2 special vertices, top and bottom center, rest are relative to this
vertices.emplace_back(Vec3f(center_x, 0.f, b_height));
vertices.emplace_back(Vec3f(center_x, 0.f, t_height));
float b_angle_start = angle_rotation - b_angle;
float t_angle_start = angle_rotation - t_angle;
const float b_angle_stop = angle_rotation + b_angle;
const int frst_id = frst_vertex_id;
const int scnd_id = frst_id + 1;
// add first side vertices and internal facets
{
const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
int id = (int)vertices.size() - 1;
facets.emplace_back(frst_id, id - 2, id - 1);
facets.emplace_back(frst_id, id - 1, id);
facets.emplace_back(frst_id, id, scnd_id);
}
// add d side vertices and facets
while (!is_approx(b_angle_start, b_angle_stop)) {
b_angle_start += b_angle_step;
t_angle_start += t_angle_step;
const Vec2f m_vec = Eigen::Vector2f(0, get_m_len(b_angle_start));
add_side_vertices(vertices, b_angle_start, t_angle_start, m_vec);
add_side_facets(facets, (int)vertices.size(), frst_id, scnd_id);
}
// add last internal facets to close the mesh
{
int id = (int)vertices.size() - 1;
facets.emplace_back(frst_id, scnd_id, id);
facets.emplace_back(frst_id, id, id - 1);
facets.emplace_back(frst_id, id - 1, id - 2);
}
};
indexed_triangle_set mesh;
mesh.vertices.reserve(2 * (3 * (2 * sectors_cnt + 1) + 2));
mesh.indices.reserve(2 * (6 * 2 * sectors_cnt + 6));
add_sub_mesh(mesh, -space_len, halfPI , 0);
add_sub_mesh(mesh, space_len, 3 * halfPI, (int)mesh.vertices.size());
return mesh;
}
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts)
{
std::vector<Vec3f> dst_vertices;

View file

@ -1,3 +1,14 @@
///|/ Copyright (c) Prusa Research 2017 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Tomáš Mészáros @tamasmeszaros, Enrico Turri @enricoturri1966, Filip Sykala @Jony01
///|/ Copyright (c) 2019 Sijmen Schoon
///|/ Copyright (c) 2016 Joseph Lenox @lordofhyphens
///|/ Copyright (c) Slic3r 2013 - 2016 Alessandro Ranellucci @alranel
///|/
///|/ ported from lib/Slic3r/TriangleMesh.pm:
///|/ Copyright (c) Slic3r 2011 - 2014 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2012 - 2013 Mark Hindess
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_TriangleMesh_hpp_
#define slic3r_TriangleMesh_hpp_
@ -337,9 +348,11 @@ indexed_triangle_set its_make_cube(double x, double y, double z);
indexed_triangle_set its_make_prism(float width, float length, float height);
indexed_triangle_set its_make_cylinder(double r, double h, double fa=(2*PI/360));
indexed_triangle_set its_make_cone(double r, double h, double fa=(2*PI/360));
indexed_triangle_set its_make_frustum(double r, double h, double fa=(2*PI/360));
indexed_triangle_set its_make_frustum_dowel(double r, double h, int sectorCount);
indexed_triangle_set its_make_pyramid(float base, float height);
indexed_triangle_set its_make_sphere(double radius, double fa);
indexed_triangle_set its_make_snap(double r, double h, float space_proportion = 0.25f, float bulge_proportion = 0.125f);
indexed_triangle_set its_convex_hull(const std::vector<Vec3f> &pts);
inline indexed_triangle_set its_convex_hull(const indexed_triangle_set &its) { return its_convex_hull(its.vertices); }

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2021 - 2023 Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Pavel Mikuš @Godrak, Lukáš Hejl @hejllukas
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "Tesselate.hpp"
@ -2300,79 +2304,4 @@ void cut_mesh(const indexed_triangle_set& mesh, float z, indexed_triangle_set* u
}
}
// BBS: implement plane cut with cgal
static Vec3d calc_plane_normal(const std::array<Vec3d, 4>& plane_points)
{
Vec3d v01 = plane_points[1] - plane_points[0];
Vec3d v12 = plane_points[2] - plane_points[1];
Vec3d plane_normal = v01.cross(v12);
plane_normal.normalize();
return plane_normal;
}
void cut_mesh
(
const indexed_triangle_set& mesh, // model object coordinate
std::array<Vec3d, 4> plane_points, // model object coordinate
indexed_triangle_set* upper,
indexed_triangle_set* lower,
bool triangulate_caps
)
{
assert(upper || lower);
if (upper == nullptr && lower == nullptr)
return;
BOOST_LOG_TRIVIAL(trace) << "cut_mesh - slicing object";
Vec3d plane_normal = calc_plane_normal(plane_points);
if (std::abs(plane_normal(0)) < EPSILON && std::abs(plane_normal(1)) < EPSILON) {
cut_mesh(mesh, plane_points[0](2), upper, lower);
return;
}
// BBS
if (std::abs(plane_normal(2)) < EPSILON) {
// keep the side on the normal direction
}
else if (plane_normal(2) < 0.0) {
std::reverse(plane_points.begin(), plane_points.end());
}
plane_normal = calc_plane_normal(plane_points);
Vec3d mid_point = { 0.0, 0.0, 0.0 };
for (auto pt : plane_points)
mid_point += pt;
mid_point /= (double)plane_points.size();
Vec3d movement = -mid_point;
Vec3d axis = { 0.0, 0.0, 0.0 };
double phi = 0.0;
Matrix3d matrix;
matrix.setIdentity();
Geometry::rotation_from_two_vectors(plane_normal, { 0.0, 0.0, 1.0 }, axis, phi, &matrix);
Vec3d angles = Geometry::extract_euler_angles(matrix);
movement = matrix * movement;
Transform3d transfo;
transfo.setIdentity();
transfo.translate(movement);
transfo.rotate(Eigen::AngleAxisd(angles(2), Vec3d::UnitZ()) * Eigen::AngleAxisd(angles(1), Vec3d::UnitY()) * Eigen::AngleAxisd(angles(0), Vec3d::UnitX()));
indexed_triangle_set mesh_temp = mesh;
its_transform(mesh_temp, transfo);
cut_mesh(mesh_temp, 0., upper, lower);
Transform3d transfo_inv = transfo.inverse();
if (upper) {
its_transform(*upper, transfo_inv);
}
if (lower) {
its_transform(*lower, transfo_inv);
}
}
} // namespace Slic3r

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2021 - 2022 Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_TriangleMeshSlicer_hpp_
#define slic3r_TriangleMeshSlicer_hpp_
@ -130,14 +134,6 @@ void cut_mesh(
indexed_triangle_set *lower,
bool triangulate_caps = true);
// BBS
void cut_mesh(
const indexed_triangle_set &mesh,
std::array<Vec3d, 4> plane_points,
indexed_triangle_set *upper,
indexed_triangle_set *lower,
bool triangulate_caps = true);
}
} // namespace Slic3r
#endif // slic3r_TriangleMeshSlicer_hpp_

View file

@ -160,6 +160,11 @@ void flush_logs();
// This type is only needed for Perl bindings to relay to Perl that the string is raw, not UTF-8 encoded.
typedef std::string local_encoded_string;
// Returns next utf8 sequence length. =number of bytes in string, that creates together one utf-8 character.
// Starting at pos. ASCII characters returns 1. Works also if pos is in the middle of the sequence.
extern size_t get_utf8_sequence_length(const std::string& text, size_t pos = 0);
extern size_t get_utf8_sequence_length(const char *seq, size_t size);
// Convert an UTF-8 encoded string into local coding.
// On Windows, the UTF-8 string is converted to a local 8-bit code page.
// On OSX and Linux, this function does no conversion and returns a copy of the source string.

View file

@ -1,3 +1,9 @@
///|/ Copyright (c) Prusa Research 2016 - 2023 Pavel Mikuš @Godrak, Oleksandra Iushchenko @YuSanka, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Filip Sykala @Jony01, David Kocík @kocikdav, Roman Beránek @zavorka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, Vojtěch Král @vojtechkral
///|/ Copyright (c) 2021 Justin Schuh @jschuh
///|/ Copyright (c) Slic3r 2013 - 2015 Alessandro Ranellucci @alranel
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Utils.hpp"
#include "I18N.hpp"
@ -1030,6 +1036,76 @@ bool is_shapes_dir(const std::string& dir)
namespace Slic3r {
size_t get_utf8_sequence_length(const std::string& text, size_t pos)
{
assert(pos < text.size());
return get_utf8_sequence_length(text.c_str() + pos, text.size() - pos);
}
size_t get_utf8_sequence_length(const char *seq, size_t size)
{
size_t length = 0;
unsigned char c = seq[0];
if (c < 0x80) { // 0x00-0x7F
// is ASCII letter
length++;
}
// Bytes 0x80 to 0xBD are trailer bytes in a multibyte sequence.
// pos is in the middle of a utf-8 sequence. Add the utf-8 trailer bytes.
else if (c < 0xC0) { // 0x80-0xBF
length++;
while (length < size) {
c = seq[length];
if (c < 0x80 || c >= 0xC0) {
break; // prevent overrun
}
length++; // add a utf-8 trailer byte
}
}
// Bytes 0xC0 to 0xFD are header bytes in a multibyte sequence.
// The number of one bits above the topmost zero bit indicates the number of bytes (including this one) in the whole sequence.
else if (c < 0xE0) { // 0xC0-0xDF
// add a utf-8 sequence (2 bytes)
if (2 > size) {
return size; // prevent overrun
}
length += 2;
}
else if (c < 0xF0) { // 0xE0-0xEF
// add a utf-8 sequence (3 bytes)
if (3 > size) {
return size; // prevent overrun
}
length += 3;
}
else if (c < 0xF8) { // 0xF0-0xF7
// add a utf-8 sequence (4 bytes)
if (4 > size) {
return size; // prevent overrun
}
length += 4;
}
else if (c < 0xFC) { // 0xF8-0xFB
// add a utf-8 sequence (5 bytes)
if (5 > size) {
return size; // prevent overrun
}
length += 5;
}
else if (c < 0xFE) { // 0xFC-0xFD
// add a utf-8 sequence (6 bytes)
if (6 > size) {
return size; // prevent overrun
}
length += 6;
}
else { // 0xFE-0xFF
// not a utf-8 sequence
length++;
}
return length;
}
// Encode an UTF-8 string to the local code page.
std::string encode_path(const char *src)
{

View file

@ -132,8 +132,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoFdmSupports.hpp
GUI/Gizmos/GLGizmoFlatten.cpp
GUI/Gizmos/GLGizmoFlatten.hpp
GUI/Gizmos/GLGizmoAdvancedCut.cpp
GUI/Gizmos/GLGizmoAdvancedCut.hpp
GUI/Gizmos/GLGizmoCut.cpp
GUI/Gizmos/GLGizmoCut.hpp
#GUI/Gizmos/GLGizmoHollow.cpp
#GUI/Gizmos/GLGizmoHollow.hpp
GUI/Gizmos/GLGizmoPainterBase.cpp

View file

@ -411,6 +411,12 @@ private:
// plane coeffs for clipping in shaders
std::array<double, 4> m_clipping_plane;
// plane coeffs for render volumes with different colors in shaders
// used by cut gizmo
std::array<double, 4> m_color_clip_plane;
bool m_use_color_clip_plane{ false };
std::array<ColorRGBA, 2> m_color_clip_plane_colors{ ColorRGBA::RED(), ColorRGBA::BLUE() };
struct Slope
{
// toggle for slope rendering
@ -482,6 +488,14 @@ public:
const std::array<float, 2>& get_z_range() const { return m_z_range; }
const std::array<double, 4>& get_clipping_plane() const { return m_clipping_plane; }
void set_use_color_clip_plane(bool use) { m_use_color_clip_plane = use; }
void set_color_clip_plane(const Vec3d& cp_normal, double offset) {
for (int i = 0; i < 3; ++i)
m_color_clip_plane[i] = -cp_normal[i];
m_color_clip_plane[3] = offset;
}
void set_color_clip_plane_colors(const std::array<ColorRGBA, 2>& colors) { m_color_clip_plane_colors = colors; }
bool is_slope_GlobalActive() const { return m_slope.isGlobalActive; }
bool is_slope_active() const { return m_slope.active; }
void set_slope_active(bool active) { m_slope.active = active; }

View file

@ -807,6 +807,10 @@ public:
bool get_use_clipping_planes() const { return m_use_clipping_planes; }
const std::array<ClippingPlane, 2> &get_clipping_planes() const { return m_clipping_planes; };
void set_use_color_clip_plane(bool use) { m_volumes.set_use_color_clip_plane(use); }
void set_color_clip_plane(const Vec3d& cp_normal, double offset) { m_volumes.set_color_clip_plane(cp_normal, offset); }
void set_color_clip_plane_colors(const std::array<ColorRGBA, 2>& colors) { m_volumes.set_color_clip_plane_colors(colors); }
void refresh_camera_scene_box();
BoundingBoxf3 volumes_bounding_box(bool current_plate_only = false) const;

View file

@ -1,5 +1,10 @@
///|/ Copyright (c) Prusa Research 2019 - 2022 Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "GLSelectionRectangle.hpp"
#include "Camera.hpp"
#include "CameraUtils.hpp"
#include "3DScene.hpp"
#include "GLCanvas3D.hpp"
#include "GUI_App.hpp"
@ -29,35 +34,19 @@ namespace GUI {
m_end_corner = mouse_position;
}
std::vector<unsigned int> GLSelectionRectangle::stop_dragging(const GLCanvas3D& canvas, const std::vector<Vec3d>& points)
std::vector<unsigned int> GLSelectionRectangle::contains(const std::vector<Vec3d>& points) const
{
std::vector<unsigned int> out;
if (!is_dragging())
return out;
m_state = Off;
const Camera& camera = wxGetApp().plater()->get_camera();
Matrix4d modelview = camera.get_view_matrix().matrix();
Matrix4d projection= camera.get_projection_matrix().matrix();
Vec4i viewport(camera.get_viewport().data());
// Convert our std::vector to Eigen dynamic matrix.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> pts(points.size(), 3);
for (size_t i=0; i<points.size(); ++i)
pts.block<1, 3>(i, 0) = points[i];
// Get the projections.
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> projections;
igl::project(pts, modelview, projection, viewport, projections);
// bounding box created from the rectangle corners - will take care of order of the corners
BoundingBox rectangle(Points{ Point(m_start_corner.cast<coord_t>()), Point(m_end_corner.cast<coord_t>()) });
const BoundingBox rectangle(Points{ Point(m_start_corner.cast<coord_t>()), Point(m_end_corner.cast<coord_t>()) });
// Iterate over all points and determine whether they're in the rectangle.
for (int i = 0; i<projections.rows(); ++i)
if (rectangle.contains(Point(projections(i, 0), canvas.get_canvas_size().get_height() - projections(i, 1))))
const Camera &camera = wxGetApp().plater()->get_camera();
Points points_2d = CameraUtils::project(camera, points);
unsigned int size = static_cast<unsigned int>(points.size());
for (unsigned int i = 0; i< size; ++i)
if (rectangle.contains(points_2d[i]))
out.push_back(i);
return out;

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2019 - 2022 Enrico Turri @enricoturri1966, Lukáš Matěna @lukasmatena
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_GLSelectionRectangle_hpp_
#define slic3r_GLSelectionRectangle_hpp_
@ -25,8 +29,8 @@ public:
void dragging(const Vec2d& mouse_position);
// Given a vector of points in world coordinates, the function returns indices of those
// that are in the rectangle. It then disables the rectangle.
std::vector<unsigned int> stop_dragging(const GLCanvas3D& canvas, const std::vector<Vec3d>& points);
// that are in the rectangle.
std::vector<unsigned int> contains(const std::vector<Vec3d>& points) const;
// Disables the rectangle.
void stop_dragging();

View file

@ -2811,7 +2811,7 @@ void ObjectList::merge(bool to_multipart_object)
}
}
void ObjectList::merge_volumes()
/*void ObjectList::merge_volumes()
{
std::vector<int> obj_idxs, vol_idxs;
get_selection_indexes(obj_idxs, vol_idxs);
@ -2839,11 +2839,11 @@ void ObjectList::merge_volumes()
else {
for (int vol_idx : vol_idxs)
selection.add_volume(last_obj_idx, vol_idx, 0, false);
}*/
}#1#
#else
wxGetApp().plater()->merge(obj_idxs[0], vol_idxs);
#endif
}
}*/
void ObjectList::layers_editing()
{

View file

@ -297,7 +297,7 @@ public:
void del_info_item(const int obj_idx, InfoItemType type);
void split();
void merge(bool to_multipart_object);
void merge_volumes(); // BBS: merge parts to single part
// void merge_volumes(); // BBS: merge parts to single part
void layers_editing();
void boolean(); // BBS: Boolean Operation of parts

View file

@ -222,6 +222,9 @@ public:
void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); }
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
virtual bool is_in_editing_mode() const { return false; }
virtual bool is_selection_rectangle_dragging() const { return false; }
protected:
float last_input_window_width = 0;
virtual bool on_init() = 0;

File diff suppressed because it is too large Load diff

View file

@ -1,72 +1,391 @@
///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_GLGizmoCut_hpp_
#define slic3r_GLGizmoCut_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
#include "slic3r/GUI/GLModel.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/CutUtils.hpp"
#include "imgui/imgui.h"
namespace Slic3r {
enum class CutConnectorType : int;
class ModelVolume;
class GLShaderProgram;
struct CutConnectorAttributes;
namespace GUI {
class Selection;
class GLGizmoCut : public GLGizmoBase
enum class SLAGizmoEventType : unsigned char;
namespace CommonGizmosDataObjects { class ObjectClipper; }
class GLGizmoCut3D : public GLGizmoBase
{
static const double Offset;
static const double Margin;
double m_cut_z{ 0.0 };
double m_max_z{ 0.0 };
double m_start_z{ 0.0 };
Vec3d m_drag_pos;
Vec3d m_drag_center;
bool m_keep_upper{ true };
bool m_keep_lower{ true };
bool m_rotate_lower{ false };
// BBS: m_do_segment
bool m_cut_to_parts {false};
bool m_do_segment{ false };
double m_segment_smoothing_alpha{ 0.5 };
int m_segment_number{ 5 };
struct CutContours
{
TriangleMesh mesh;
GLModel contours;
double cut_z{ 0.0 };
Vec3d position{ Vec3d::Zero() };
Vec3d shift{ Vec3d::Zero() };
ObjectID object_id;
int instance_idx{ -1 };
enum GrabberID {
X = 0,
Y,
Z,
CutPlane,
CutPlaneZRotation,
CutPlaneXMove,
CutPlaneYMove,
Count,
};
CutContours m_cut_contours;
Transform3d m_rotation_m{ Transform3d::Identity() };
double m_snap_step{ 1.0 };
int m_connectors_group_id;
// archived values
Vec3d m_ar_plane_center { Vec3d::Zero() };
Transform3d m_start_dragging_m{ Transform3d::Identity() };
Vec3d m_plane_center{ Vec3d::Zero() };
// data to check position of the cut palne center on gizmo activation
Vec3d m_min_pos{ Vec3d::Zero() };
Vec3d m_max_pos{ Vec3d::Zero() };
Vec3d m_bb_center{ Vec3d::Zero() };
Vec3d m_center_offset{ Vec3d::Zero() };
BoundingBoxf3 m_bounding_box;
BoundingBoxf3 m_transformed_bounding_box;
// values from RotationGizmo
double m_radius{ 0.0 };
double m_grabber_radius{ 0.0 };
double m_grabber_connection_len{ 0.0 };
Vec3d m_cut_plane_start_move_pos {Vec3d::Zero()};
double m_snap_coarse_in_radius{ 0.0 };
double m_snap_coarse_out_radius{ 0.0 };
double m_snap_fine_in_radius{ 0.0 };
double m_snap_fine_out_radius{ 0.0 };
// dragging angel in hovered axes
double m_angle{ 0.0 };
TriangleMesh m_connector_mesh;
// workaround for using of the clipping plane normal
Vec3d m_clp_normal{ Vec3d::Ones() };
Vec3d m_line_beg{ Vec3d::Zero() };
Vec3d m_line_end{ Vec3d::Zero() };
Vec2d m_ldown_mouse_position{ Vec2d::Zero() };
GLModel m_grabber_connection;
GLModel m_cut_line;
PickingModel m_plane;
PickingModel m_sphere;
PickingModel m_cone;
PickingModel m_cube;
std::map<CutConnectorAttributes, PickingModel> m_shapes;
std::vector<std::shared_ptr<SceneRaycasterItem>> m_raycasters;
GLModel m_circle;
GLModel m_scale;
GLModel m_snap_radii;
GLModel m_reference_radius;
GLModel m_angle_arc;
Vec3d m_old_center;
Vec3d m_cut_normal;
struct InvalidConnectorsStatistics
{
unsigned int outside_cut_contour;
unsigned int outside_bb;
bool is_overlap;
void invalidate() {
outside_cut_contour = 0;
outside_bb = 0;
is_overlap = false;
}
} m_info_stats;
bool m_keep_upper{ true };
bool m_keep_lower{ true };
bool m_keep_as_parts{ false };
bool m_place_on_cut_upper{ true };
bool m_place_on_cut_lower{ false };
bool m_rotate_upper{ false };
bool m_rotate_lower{ false };
// Input params for cut with tongue and groove
Cut::Groove m_groove;
bool m_groove_editing { false };
bool m_is_slider_editing_done { false };
// Input params for cut with snaps
float m_snap_bulge_proportion{ 0.15f };
float m_snap_space_proportion{ 0.3f };
bool m_hide_cut_plane{ false };
bool m_connectors_editing{ false };
bool m_cut_plane_as_circle{ false };
float m_connector_depth_ratio{ 3.f };
float m_connector_size{ 2.5f };
float m_connector_angle{ 0.f };
float m_connector_depth_ratio_tolerance{ 0.1f };
float m_connector_size_tolerance{ 0.f };
float m_label_width{ 0.f };
float m_control_width{ 200.f };
bool m_imperial_units{ false };
float m_contour_width{ 0.4f };
float m_cut_plane_radius_koef{ 1.5f };
float m_shortcut_label_width{ -1.f };
mutable std::vector<bool> m_selected; // which pins are currently selected
int m_selected_count{ 0 };
GLSelectionRectangle m_selection_rectangle;
std::vector<size_t> m_invalid_connectors_idxs;
bool m_was_cut_plane_dragged { false };
bool m_was_contour_selected { false };
// Vertices of the groove used to detection if groove is valid
std::vector<Vec3d> m_groove_vertices;
class PartSelection {
public:
PartSelection() = default;
PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc);
PartSelection(const ModelObject* mo, int instance_idx_in);
~PartSelection() { m_model.clear_objects(); }
struct Part {
GLModel glmodel;
MeshRaycaster raycaster;
bool selected;
bool is_modifier;
};
void render(const Vec3d* normal, GLModel& sphere_model);
void toggle_selection(const Vec2d& mouse_pos);
void turn_over_selection();
ModelObject* model_object() { return m_model.objects.front(); }
bool valid() const { return m_valid; }
bool is_one_object() const;
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;
std::vector<Part> m_parts;
bool m_valid = false;
std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below
std::vector<size_t> m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object)
std::vector<Vec3d> m_contour_points; // Debugging
std::vector<std::vector<Vec3d>> m_debug_pts; // Debugging
void add_object(const ModelObject* object);
};
PartSelection m_part_selection;
bool m_show_shortcuts{ false };
std::vector<std::pair<wxString, wxString>> m_shortcuts;
enum class CutMode {
cutPlanar
, cutTongueAndGroove
//, cutGrig
//,cutRadial
//,cutModular
};
enum class CutConnectorMode {
Auto
, Manual
};
std::vector<std::string> m_modes;
size_t m_mode{ size_t(CutMode::cutPlanar) };
std::vector<std::string> m_connector_modes;
CutConnectorMode m_connector_mode{ CutConnectorMode::Manual };
std::vector<std::string> m_connector_types;
CutConnectorType m_connector_type;
std::vector<std::string> m_connector_styles;
int m_connector_style;
std::vector<std::string> m_connector_shapes;
int m_connector_shape_id;
std::vector<std::string> m_axis_names;
std::map<std::string, wxString> m_part_orientation_names;
std::map<std::string, std::string> m_labels_map;
public:
GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
double get_cut_z() const { return m_cut_z; }
void set_cut_z(double cut_z);
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
std::string get_tooltip() const override;
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_contours = true);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
bool is_in_editing_mode() const override { return m_connectors_editing; }
bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
bool is_looking_forward() const;
/// <summary>
/// Drag of plane
/// </summary>
/// <param name="mouse_event">Keep information about mouse click</param>
/// <returns>Return True when use the information otherwise False.</returns>
bool on_mouse(const wxMouseEvent &mouse_event) override;
void shift_cut(double delta);
void rotate_vec3d_around_plane_center(Vec3d&vec);
void put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset);
void update_clipper();
void invalidate_cut_plane();
BoundingBoxf3 bounding_box() const;
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, const Transform3d& rotation_m = Transform3d::Identity()) const;
protected:
virtual bool on_init() override;
virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual std::string on_get_name() const override;
virtual void on_set_state() override;
virtual bool on_is_activable() const override;
virtual void on_start_dragging() override;
virtual void on_update(const UpdateData& data) override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
bool on_init() override;
void on_load(cereal::BinaryInputArchive&ar) override;
void on_save(cereal::BinaryOutputArchive&ar) const override;
std::string on_get_name() const override;
void on_set_state() override;
CommonGizmosDataID on_get_requirements() const override;
void on_set_hover_id() override;
bool on_is_activable() const override;
bool on_is_selectable() const override;
Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const;
void dragging_grabber_move(const GLGizmoBase::UpdateData &data);
void dragging_grabber_rotation(const GLGizmoBase::UpdateData &data);
void dragging_connector(const GLGizmoBase::UpdateData &data);
void on_dragging(const UpdateData&data) override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_render() override;
void render_debug_input_window(float x);
void adjust_window_position(float x, float y, float bottom_limit);
void unselect_all_connectors();
void select_all_connectors();
void render_shortcuts();
void apply_selected_connectors(std::function<void(size_t idx)> apply_fn);
void render_connectors_input_window(CutConnectors &connectors);
void render_build_size();
void reset_cut_plane();
void set_connectors_editing(bool connectors_editing);
void flip_cut_plane();
void process_contours();
void reset_cut_by_contours();
void render_flip_plane_button(bool disable_pred = false);
void add_vertical_scaled_interval(float interval);
void add_horizontal_scaled_interval(float interval);
void add_horizontal_shift(float shift);
void render_color_marker(float size, const ImU32& color);
void render_groove_float_input(const std::string &label, float &in_val, const float &init_val, float &in_tolerance);
void render_groove_angle_input(const std::string &label, float &in_val, const float &init_val, float min_val, float max_val);
bool render_angle_input(const std::string& label, float& in_val, const float& init_val, float min_val, float max_val);
void render_snap_specific_input(const std::string& label, const wxString& tooltip, float& in_val, const float& init_val, const float min_val, const float max_val);
void render_cut_plane_input_window(CutConnectors &connectors);
void init_input_window_data(CutConnectors &connectors);
void render_input_window_warning() const;
bool add_connector(CutConnectors&connectors, const Vec2d&mouse_position);
bool delete_selected_connectors(CutConnectors&connectors);
void select_connector(int idx, bool select);
bool is_selection_changed(bool alt_down, bool shift_down);
void process_selection_rectangle(CutConnectors &connectors);
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
void update_raycasters_for_picking();
void set_volumes_picking_state(bool state);
void update_raycasters_for_picking_transform();
void update_plane_model();
void on_render_input_window(float x, float y, float bottom_limit) override;
bool wants_enter_leave_snapshots() const override { return true; }
std::string get_gizmo_entering_text() const override { return _u8L("Entering Cut gizmo"); }
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); }
std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); }
void data_changed(bool is_serializing) override;
Transform3d get_cut_matrix(const Selection& selection);
private:
void perform_cut(const Selection& selection);
double calc_projection(const Linef3& mouse_ray) const;
BoundingBoxf3 bounding_box() const;
void update_contours();
void set_center(const Vec3d&center, bool update_tbb = false);
void switch_to_mode(size_t new_mode);
bool render_cut_mode_combo();
bool render_combo(const std::string&label, const std::vector<std::string>&lines, int&selection_idx);
bool render_double_input(const std::string& label, double& value_in);
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in, float min_val = -0.1f, float max_tolerance = -0.1f);
void render_move_center_input(int axis);
void render_connect_mode_radio_button(CutConnectorMode mode);
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
bool render_connect_type_radio_button(CutConnectorType type);
bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
void render_connectors();
bool can_perform_cut() const;
bool has_valid_groove() const;
bool has_valid_contour() const;
void apply_connectors_in_model(ModelObject* mo, int &dowels_count);
bool cut_line_processing() const;
void discard_cut_line_processing();
void apply_color_clip_plane_colors();
void render_cut_plane();
static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
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();
void perform_cut(const Selection&selection);
void set_center_pos(const Vec3d&center_pos, bool update_tbb = false);
void update_bb();
void init_picking_models();
void init_rendering_items();
void render_clipper_cut();
void clear_selection();
void reset_connectors();
void init_connector_shapes();
void update_connector_shape();
void validate_connector_settings();
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
void check_and_update_connectors_state();
void toggle_model_objects_visibility();
indexed_triangle_set its_make_groove_plane();
indexed_triangle_set get_connector_mesh(CutConnectorAttributes connector_attributes);
void apply_cut_connectors(ModelObject* mo, const std::string& connector_name);
};
} // namespace GUI

View file

@ -20,8 +20,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
// BBS
#include "slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp"
//#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
@ -193,7 +192,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, m_is_dark ? "toolbar_rotate_dark.svg" : "toolbar_rotate.svg", EType::Rotate, &m_object_manipulation));
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, m_is_dark ? "toolbar_scale_dark.svg" : "toolbar_scale.svg", EType::Scale, &m_object_manipulation));
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, m_is_dark ? "toolbar_flatten_dark.svg" : "toolbar_flatten.svg", EType::Flatten));
m_gizmos.emplace_back(new GLGizmoAdvancedCut(m_parent, m_is_dark ? "toolbar_cut_dark.svg" : "toolbar_cut.svg", EType::Cut));
m_gizmos.emplace_back(new GLGizmoCut3D(m_parent, m_is_dark ? "toolbar_cut_dark.svg" : "toolbar_cut.svg", EType::Cut));
m_gizmos.emplace_back(new GLGizmoMeshBoolean(m_parent, m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg", EType::MeshBoolean));
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, m_is_dark ? "toolbar_support_dark.svg" : "toolbar_support.svg", EType::FdmSupports));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam));
@ -446,7 +445,7 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
else if (m_current == Measure)
return dynamic_cast<GLGizmoMeasure*>(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Cut)
return dynamic_cast<GLGizmoAdvancedCut *>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == MeshBoolean)
return dynamic_cast<GLGizmoMeshBoolean*>(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else
@ -861,13 +860,10 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
// m_parent.set_cursor(GLCanvas3D::Cross);
processed = true;
}
else*/ if (m_current == Cut)
{
// BBS
#if 0
else*/ if (m_current == Cut) {
auto do_move = [this, &processed](double delta_z) {
GLGizmoAdvancedCut* cut = dynamic_cast<GLGizmoAdvancedCut*>(get_current());
cut->set_cut_z(delta_z + cut->get_cut_z());
GLGizmoCut3D* cut = dynamic_cast<GLGizmoCut3D*>(get_current());
cut->shift_cut(delta_z);
processed = true;
};
@ -875,10 +871,13 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
{
case WXK_NUMPAD_UP: case WXK_UP: { do_move(1.0); break; }
case WXK_NUMPAD_DOWN: case WXK_DOWN: { do_move(-1.0); break; }
case WXK_SHIFT : case WXK_ALT: {
processed = get_current()->is_in_editing_mode();
}
default: { break; }
}
#endif
} else if (m_current == Simplify && keyCode == WXK_ESCAPE) {
}
else if (m_current == Simplify && keyCode == WXK_ESCAPE) {
GLGizmoSimplify *simplify = dynamic_cast<GLGizmoSimplify *>(get_current());
if (simplify != nullptr)
processed = simplify->on_esc_key_down();

View file

@ -511,10 +511,12 @@ void ImGuiWrapper::render()
m_new_frame_open = false;
}
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text, float wrap_width) const
ImVec2 ImGuiWrapper::calc_text_size(const wxString &text,
bool hide_text_after_double_hash,
float wrap_width) const
{
auto text_utf8 = into_u8(text);
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, false, wrap_width);
ImVec2 size = ImGui::CalcTextSize(text_utf8.c_str(), NULL, hide_text_after_double_hash, wrap_width);
/*#ifdef __linux__
size.x *= m_style_scaling;
@ -773,16 +775,30 @@ void ImGuiWrapper::end()
ImGui::End();
}
bool ImGuiWrapper::button(const wxString &label)
bool ImGuiWrapper::button(const wxString &label, const wxString& tooltip)
{
auto label_utf8 = into_u8(label);
return ImGui::Button(label_utf8.c_str());
const bool ret = ImGui::Button(label_utf8.c_str());
if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) {
auto tooltip_utf8 = into_u8(tooltip);
ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr);
}
return ret;
}
bool ImGuiWrapper::bbl_button(const wxString &label)
bool ImGuiWrapper::bbl_button(const wxString &label, const wxString& tooltip)
{
auto label_utf8 = into_u8(label);
return ImGui::BBLButton(label_utf8.c_str());
const bool ret = ImGui::BBLButton(label_utf8.c_str());
if (!tooltip.IsEmpty() && ImGui::IsItemHovered()) {
auto tooltip_utf8 = into_u8(tooltip);
ImGui::SetTooltip(tooltip_utf8.c_str(), nullptr);
}
return ret;
}
bool ImGuiWrapper::button(const wxString& label, float width, float height)
@ -791,6 +807,17 @@ bool ImGuiWrapper::button(const wxString& label, float width, float height)
return ImGui::Button(label_utf8.c_str(), ImVec2(width, height));
}
bool ImGuiWrapper::button(const wxString& label, const ImVec2 &size, bool enable)
{
disabled_begin(!enable);
auto label_utf8 = into_u8(label);
bool res = ImGui::Button(label_utf8.c_str(), size);
disabled_end();
return (enable) ? res : false;
}
bool ImGuiWrapper::radio_button(const wxString &label, bool active)
{
auto label_utf8 = into_u8(label);
@ -965,6 +992,8 @@ bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float
m_last_slider_status.edited = ImGui::IsItemEdited();
m_last_slider_status.clicked = ImGui::IsItemClicked();
m_last_slider_status.deactivated_after_edit = ImGui::IsItemDeactivatedAfterEdit();
if (!m_last_slider_status.can_take_snapshot)
m_last_slider_status.can_take_snapshot = ImGui::IsItemClicked();
if (!tooltip.empty() && ImGui::IsItemHovered())
this->tooltip(into_u8(tooltip).c_str(), max_tooltip_width);
@ -1103,25 +1132,34 @@ bool ImGuiWrapper::image_button(const wchar_t icon, const wxString& tooltip)
return res;
}
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection)
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/)
{
return combo(into_u8(label), options, selection, flags, label_width, item_width);
}
bool ImGuiWrapper::combo(const std::string& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags/* = 0*/, float label_width/* = 0.0f*/, float item_width/* = 0.0f*/)
{
// this is to force the label to the left of the widget:
const bool hidden_label = boost::starts_with(label, "##");
if (!label.empty() && !hidden_label) {
text(label);
ImGui::SameLine();
ImGui::SameLine(label_width);
}
ImGui::PushItemWidth(item_width);
int selection_out = selection;
bool res = false;
const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : "";
if (ImGui::BeginCombo("", selection_str)) {
if (ImGui::BeginCombo(hidden_label ? label.c_str() : ("##" + label).c_str(), selection_str, flags)) {
for (int i = 0; i < (int)options.size(); i++) {
if (ImGui::Selectable(options[i].c_str(), i == selection)) {
selection_out = i;
res = true;
}
}
ImGui::EndCombo();
res = true;
}
selection = selection_out;

View file

@ -74,6 +74,11 @@ public:
bool edited { false };
bool clicked { false };
bool deactivated_after_edit { false };
// flag to indicate possibility to take snapshot from the slider value
// It's used from Gizmos to take snapshots just from the very beginning of the editing
bool can_take_snapshot { false };
// When Undo/Redo snapshot is taken, then call this function
void invalidate_snapshot() { can_take_snapshot = false; }
};
ImGuiWrapper();
@ -95,12 +100,13 @@ public:
float scaled(float x) const { return x * m_font_size; }
ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size, y * m_font_size); }
ImVec2 calc_text_size(const wxString &text, float wrap_width = -1.0f) const;
ImVec2 calc_text_size(const wxString &text, bool hide_text_after_double_hash = false, float wrap_width = -1.0f) const;
ImVec2 calc_button_size(const wxString &text, const ImVec2 &button_size = ImVec2(0, 0)) const;
ImVec2 get_item_spacing() const;
float get_slider_float_height() const;
const LastSliderStatus& get_last_slider_status() const { return m_last_slider_status; }
LastSliderStatus& get_last_slider_status() { return m_last_slider_status; }
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
void set_next_window_bg_alpha(float alpha);
@ -118,9 +124,10 @@ public:
bool begin(const wxString& name, bool* close, int flags = 0);
void end();
bool button(const wxString &label);
bool bbl_button(const wxString &label);
bool button(const wxString &label, const wxString& tooltip = {});
bool bbl_button(const wxString &label, const wxString& tooltip = {});
bool button(const wxString& label, float width, float height);
bool button(const wxString& label, const ImVec2 &size, bool enable); // default size = ImVec2(0.f, 0.f)
bool radio_button(const wxString &label, bool active);
bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f");
@ -157,7 +164,9 @@ public:
bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0);
bool image_button(const wchar_t icon, const wxString& tooltip = L"");
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection); // Use -1 to not mark any option as selected
// Use selection = -1 to not mark any option as selected
bool combo(const std::string& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f);
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection, ImGuiComboFlags flags = 0, float label_width = 0.0f, float item_width = 0.0f);
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel);
void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str,
Search::OptionViewParameters &view_params,

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Vojtěch Bubník @bubnikv, Enrico Turri @enricoturri1966, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Vojtěch Král @vojtechkral
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "MsgDialog.hpp"
#include <wx/settings.h>
@ -411,6 +415,58 @@ InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString&
finalize();
}
wxString get_wraped_wxString(const wxString& in, size_t line_len /*=80*/)
{
wxString out;
for (size_t i = 0; i < in.size();) {
// Overwrite the character (space or newline) starting at ibreak?
bool overwrite = false;
// UTF8 representation of wxString.
// Where to break the line, index of character at the start of a UTF-8 sequence.
size_t ibreak = size_t(-1);
// Overwrite the character at ibreak (it is a whitespace) or not?
size_t j = i;
for (size_t cnt = 0; j < in.size();) {
if (bool newline = in[j] == '\n'; in[j] == ' ' || in[j] == '\t' || newline) {
// Overwrite the whitespace.
ibreak = j ++;
overwrite = true;
if (newline)
break;
} else if (in[j] == '/'
#ifdef _WIN32
|| in[j] == '\\'
#endif // _WIN32
) {
// Insert after the slash.
ibreak = ++ j;
overwrite = false;
} else
j += get_utf8_sequence_length(in.c_str() + j, in.size() - j);
if (++ cnt == line_len) {
if (ibreak == size_t(-1)) {
ibreak = j;
overwrite = false;
}
break;
}
}
if (j == in.size()) {
out.append(in.begin() + i, in.end());
break;
}
assert(ibreak != size_t(-1));
out.append(in.begin() + i, in.begin() + ibreak);
out.append('\n');
i = ibreak;
if (overwrite)
++ i;
}
return out;
}
// InfoDialog
DownloadDialog::DownloadDialog(wxWindow *parent, const wxString &msg, const wxString &title, bool is_marked_msg /* = false*/, long style /* = wxOK | wxICON_INFORMATION*/)
: MsgDialog(parent, title, msg, style), msg(msg)

View file

@ -1,3 +1,7 @@
///|/ Copyright (c) Prusa Research 2018 - 2022 Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_MsgDialog_hpp_
#define slic3r_MsgDialog_hpp_
@ -128,6 +132,8 @@ public:
virtual ~WarningDialog() = default;
};
wxString get_wraped_wxString(const wxString& text_in, size_t line_len = 80);
#if 1
// Generic static line, used intead of wxStaticLine
//class StaticLine: public wxTextCtrl

View file

@ -1,3 +1,22 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Vojtěch Král @vojtechkral
///|/ Copyright (c) 2022 Michael Kirsch
///|/ Copyright (c) 2021 Boleslaw Ciesielski
///|/ Copyright (c) 2019 John Drake @foxox
///|/
///|/ ported from lib/Slic3r/GUI/Plater.pm:
///|/ Copyright (c) Prusa Research 2016 - 2019 Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Tomáš Mészáros @tamasmeszaros
///|/ Copyright (c) 2018 Martin Loidl @LoidlM
///|/ Copyright (c) 2017 Matthias Gazzari @qtux
///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2017 Joseph Lenox @lordofhyphens
///|/ Copyright (c) 2015 Daren Schwenke
///|/ Copyright (c) 2014 Mark Hindess
///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake
///|/ Copyright (c) 2012 Henrik Brix Andersen @henrikbrixandersen
///|/ Copyright (c) 2012 Sam Wong
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Plater.hpp"
#include "libslic3r/Config.hpp"
@ -128,6 +147,7 @@
#include "Gizmos/GLGizmosManager.hpp"
#endif // __APPLE__
#include <libslic3r/CutUtils.hpp>
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
#include "WipeTowerDialog.hpp"
@ -8273,14 +8293,6 @@ void Plater::add_model(bool imperial_units, std::string fname)
wxGetApp().mainframe->update_title();
}
}
std::array<Vec3d, 4> get_cut_plane(const BoundingBoxf3& bbox, const double& cut_height) {
std::array<Vec3d, 4> plane_pts;
plane_pts[0] = Vec3d(bbox.min(0), bbox.min(1), cut_height);
plane_pts[1] = Vec3d(bbox.max(0), bbox.min(1), cut_height);
plane_pts[2] = Vec3d(bbox.max(0), bbox.max(1), cut_height);
plane_pts[3] = Vec3d(bbox.min(0), bbox.max(1), cut_height);
return plane_pts;
}
void Plater::calib_pa(const Calib_Params& params)
{
@ -8403,6 +8415,36 @@ void Plater::_calib_pa_pattern(const Calib_Params& params)
changed_objects({ 0 });
}
void Plater::cut_horizontal(size_t obj_idx, size_t instance_idx, double z, 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");
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
return;
wxBusyCursor wait;
const auto new_objects = Cut::cut_horizontal(object, instance_idx, z, attributes);
remove(obj_idx);
p->load_model_objects(new_objects);
// now process all updates of the 3d scene
update();
// Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(),
// which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call
for (size_t idx = 0; idx < p->model.objects.size(); idx++)
wxGetApp().obj_list()->update_info_items(idx);
Selection& selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i)
selection.add_object((unsigned int)(last_id - i), i == 0);
}
void Plater::_calib_pa_tower(const Calib_Params& params) {
add_model(false, Slic3r::resources_dir() + "/calib/pressure_advance/tower_with_seam.stl");
@ -8439,8 +8481,7 @@ void Plater::_calib_pa_tower(const Calib_Params& params) {
auto new_height = std::ceil((params.end - params.start) / params.step) + 1;
auto obj_bb = model().objects[0]->bounding_box();
if (new_height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts = get_cut_plane(obj_bb, new_height);
cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_horizontal(0, 0, new_height, ModelObjectCutAttribute::KeepLower);
}
_calib_pa_select_added_objects();
@ -8581,8 +8622,7 @@ void Plater::calib_temp(const Calib_Params& params) {
// add EPSILON offset to avoid cutting at the exact location where the flat surface is
auto new_height = block_count * 10.0 + EPSILON;
if (new_height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts = get_cut_plane(obj_bb, new_height);
cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_horizontal(0, 0, new_height, ModelObjectCutAttribute::KeepLower);
}
}
@ -8592,8 +8632,7 @@ void Plater::calib_temp(const Calib_Params& params) {
if(block_count > 0){
auto new_height = block_count * 10.0 + EPSILON;
if (new_height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts = get_cut_plane(obj_bb, new_height);
cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepUpper);
cut_horizontal(0, 0, new_height, ModelObjectCutAttribute::KeepUpper);
}
}
@ -8661,8 +8700,7 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
auto obj_bb = obj->bounding_box();
auto height = (params.end - params.start + 1) / params.step;
if (height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts = get_cut_plane(obj_bb, height);
cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
}
auto new_params = params;
@ -8710,8 +8748,7 @@ void Plater::calib_retraction(const Calib_Params& params)
auto obj_bb = obj->bounding_box();
auto height = 1.0 + 0.4 + ((params.end - params.start)) / params.step;
if (height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts = get_cut_plane(obj_bb, height);
cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
}
p->background_process.fff_print()->set_calib_params(params);
@ -8752,8 +8789,7 @@ void Plater::calib_VFA(const Calib_Params& params)
auto obj_bb = model().objects[0]->bounding_box();
auto height = 5 * ((params.end - params.start) / params.step + 1);
if (height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts = get_cut_plane(obj_bb, height);
cut(0, 0, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_horizontal(0, 0, height, ModelObjectCutAttribute::KeepLower);
}
p->background_process.fff_print()->set_calib_params(params);
@ -9858,27 +9894,16 @@ void Plater::convert_unit(ConversionType conv_type)
}
}
// BBS: replace z with plane_points
void Plater::cut(size_t obj_idx, size_t instance_idx, std::array<Vec3d, 4> plane_points, ModelObjectCutAttributes attributes)
void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects)
{
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
auto *object = p->model.objects[obj_idx];
model().delete_object(obj_idx);
sidebar().obj_list()->delete_object_from_list(obj_idx);
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower))
return;
wxBusyCursor wait;
// BBS: replace z with plane_points
const auto new_objects = object->cut(instance_idx, plane_points, attributes);
remove(obj_idx);
p->load_model_objects(new_objects);
// suppress to call selection update for Object List to avoid call of early Gizmos on/off update
p->load_model_objects(new_objects, false, false);
// now process all updates of the 3d scene
update();
// Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(),
// which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call
for (size_t idx = 0; idx < p->model.objects.size(); idx++)
@ -9888,62 +9913,10 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, std::array<Vec3d, 4> plane
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i)
selection.add_object((unsigned int)(last_id - i), i == 0);
}
// BBS
void Plater::segment(size_t obj_idx, size_t instance_idx, double smoothing_alpha, int segment_number)
{
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");
Plater::TakeSnapshot snapshot(this, "Segment");
wxBusyCursor wait;
// real process
PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
const size_t filament_cnt = print_tech != ptFFF ? 1 : preset_bundle.filament_presets.size();
const auto new_objects = object->segment(instance_idx, filament_cnt, smoothing_alpha, segment_number);
remove(obj_idx);
p->load_model_objects(new_objects);
Selection& selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i)
{
selection.add_object((unsigned int)(last_id - i), i == 0);
}
}
// BBS
void Plater::merge(size_t obj_idx, std::vector<int>& vol_indeces)
{
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
auto* object = p->model.objects[obj_idx];
Plater::TakeSnapshot snapshot(this, "Merge");
wxBusyCursor wait;
// real process
PresetBundle& preset_bundle = *wxGetApp().preset_bundle;
const auto print_tech = preset_bundle.printers.get_edited_preset().printer_technology();
// BBS
const size_t filament_cnt = print_tech != ptFFF ? 1 : preset_bundle.filament_presets.size();
const auto new_objects = object->merge_volumes(vol_indeces);
remove(obj_idx);
p->load_model_objects(new_objects);
Selection& selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < new_objects.size(); ++i)
{
selection.add_object((unsigned int)(last_id - i), i == 0);
}
// UIThreadWorker w;
// arrange(w, true);
// w.wait_for_idle();
}
void Plater::export_gcode(bool prefer_removable)

View file

@ -1,3 +1,19 @@
///|/ Copyright (c) Prusa Research 2018 - 2023 Tomáš Mészáros @tamasmeszaros, Oleksandra Iushchenko @YuSanka, Enrico Turri @enricoturri1966, David Kocík @kocikdav, Lukáš Hejl @hejllukas, Vojtěch Bubník @bubnikv, Lukáš Matěna @lukasmatena, Pavel Mikuš @Godrak, Filip Sykala @Jony01, Vojtěch Král @vojtechkral
///|/
///|/ ported from lib/Slic3r/GUI/Plater.pm:
///|/ Copyright (c) Prusa Research 2016 - 2019 Vojtěch Bubník @bubnikv, Vojtěch Král @vojtechkral, Enrico Turri @enricoturri1966, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Tomáš Mészáros @tamasmeszaros
///|/ Copyright (c) 2018 Martin Loidl @LoidlM
///|/ Copyright (c) 2017 Matthias Gazzari @qtux
///|/ Copyright (c) Slic3r 2012 - 2016 Alessandro Ranellucci @alranel
///|/ Copyright (c) 2017 Joseph Lenox @lordofhyphens
///|/ Copyright (c) 2015 Daren Schwenke
///|/ Copyright (c) 2014 Mark Hindess
///|/ Copyright (c) 2012 Mike Sheldrake @mesheldrake
///|/ Copyright (c) 2012 Henrik Brix Andersen @henrikbrixandersen
///|/ Copyright (c) 2012 Sam Wong
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_Plater_hpp_
#define slic3r_Plater_hpp_
@ -25,6 +41,7 @@
#include "libslic3r/PrintBase.hpp"
#include "libslic3r/calib.hpp"
#include "libslic3r/CutUtils.hpp"
#define FILAMENT_SYSTEM_COLORS_NUM 16
@ -41,8 +58,6 @@ class BuildVolume;
enum class BuildVolume_Type : unsigned char;
class Model;
class ModelObject;
enum class ModelObjectCutAttribute : int;
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
class ModelInstance;
class Print;
class SLAPrint;
@ -323,12 +338,7 @@ public:
void scale_selection_to_fit_print_volume();
void convert_unit(ConversionType conv_type);
// BBS: replace z with plane_points
void cut(size_t obj_idx, size_t instance_idx, std::array<Vec3d, 4> plane_points, ModelObjectCutAttributes attributes);
// BBS: segment model with CGAL
void segment(size_t obj_idx, size_t instance_idx, double smoothing_alpha=0.5, int segment_number=5);
void merge(size_t obj_idx, std::vector<int>& vol_indeces);
void apply_cut_object_to_model(size_t init_obj_idx, const ModelObjectPtrs& cut_objects);
void send_to_printer(bool isall = false);
void export_gcode(bool prefer_removable);
@ -742,6 +752,8 @@ private:
void _calib_pa_tower(const Calib_Params& params);
void _calib_pa_select_added_objects();
void cut_horizontal(size_t obj_idx, size_t instance_idx, double z, ModelObjectCutAttributes attributes);
friend class SuppressBackgroundProcessingUpdate;
};

View file

@ -4,6 +4,7 @@
#include "../GUI/DeviceManager.hpp"
#include "../GUI/Jobs/ProgressIndicator.hpp"
#include "../GUI/PartPlate.hpp"
#include "libslic3r/CutUtils.hpp"
#include "libslic3r/Model.hpp"
@ -133,7 +134,7 @@ bool CalibUtils::validate_input_flow_ratio(wxString flow_ratio, float* output_va
return true;
}
static void cut_model(Model &model, std::array<Vec3d, 4> plane_points, ModelObjectCutAttributes attributes)
static void cut_model(Model &model, double z, ModelObjectCutAttributes attributes)
{
size_t obj_idx = 0;
size_t instance_idx = 0;
@ -142,7 +143,7 @@ static void cut_model(Model &model, std::array<Vec3d, 4> plane_points, ModelObje
auto* object = model.objects[0];
const auto new_objects = object->cut(instance_idx, plane_points, attributes);
const auto new_objects = Cut::cut_horizontal(object, instance_idx, z, attributes);
model.delete_object(obj_idx);
for (ModelObject *model_object : new_objects) {
@ -173,16 +174,6 @@ static void read_model_from_file(const std::string& input_file, Model& model)
object->ensure_on_bed();
}
std::array<Vec3d, 4> get_cut_plane_points(const BoundingBoxf3 &bbox, const double &cut_height)
{
std::array<Vec3d, 4> plane_pts;
plane_pts[0] = Vec3d(bbox.min(0), bbox.min(1), cut_height);
plane_pts[1] = Vec3d(bbox.max(0), bbox.min(1), cut_height);
plane_pts[2] = Vec3d(bbox.max(0), bbox.max(1), cut_height);
plane_pts[3] = Vec3d(bbox.min(0), bbox.max(1), cut_height);
return plane_pts;
}
void CalibUtils::calib_PA(const X1CCalibInfos& calib_infos, int mode, wxString& error_message)
{
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
@ -564,12 +555,7 @@ void CalibUtils::calib_temptue(const CalibInfo &calib_info, wxString &error_mess
// add EPSILON offset to avoid cutting at the exact location where the flat surface is
auto new_height = block_count * 10.0 + EPSILON;
if (new_height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts;
plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), new_height);
plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), new_height);
plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), new_height);
plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), new_height);
cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_model(model, new_height, ModelObjectCutAttribute::KeepLower);
}
}
@ -579,12 +565,7 @@ void CalibUtils::calib_temptue(const CalibInfo &calib_info, wxString &error_mess
if (block_count > 0) {
auto new_height = block_count * 10.0 + EPSILON;
if (new_height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts;
plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), new_height);
plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), new_height);
plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), new_height);
plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), new_height);
cut_model(model, plane_pts, ModelObjectCutAttribute::KeepUpper);
cut_model(model, new_height, ModelObjectCutAttribute::KeepUpper);
}
}
@ -670,12 +651,7 @@ void CalibUtils::calib_max_vol_speed(const CalibInfo &calib_info, wxString &erro
auto obj_bb = obj->bounding_box();
double height = (params.end - params.start + 1) / params.step;
if (height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts;
plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), height);
plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), height);
plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), height);
plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), height);
cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_model(model, height, ModelObjectCutAttribute::KeepLower);
}
auto new_params = params;
@ -731,12 +707,7 @@ void CalibUtils::calib_VFA(const CalibInfo &calib_info, wxString &error_message)
auto obj_bb = model.objects[0]->bounding_box();
auto height = 5 * ((params.end - params.start) / params.step + 1);
if (height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts;
plane_pts[0] = Vec3d(obj_bb.min(0), obj_bb.min(1), height);
plane_pts[1] = Vec3d(obj_bb.max(0), obj_bb.min(1), height);
plane_pts[2] = Vec3d(obj_bb.max(0), obj_bb.max(1), height);
plane_pts[3] = Vec3d(obj_bb.min(0), obj_bb.max(1), height);
cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_model(model, height, ModelObjectCutAttribute::KeepLower);
}
else {
error_message = _L("The start, end or step is not valid value.");
@ -790,8 +761,7 @@ void CalibUtils::calib_retraction(const CalibInfo &calib_info, wxString &error_m
auto obj_bb = obj->bounding_box();
auto height = 1.0 + 0.4 + ((params.end - params.start)) / params.step;
if (height < obj_bb.size().z()) {
std::array<Vec3d, 4> plane_pts = get_cut_plane_points(obj_bb, height);
cut_model(model, plane_pts, ModelObjectCutAttribute::KeepLower);
cut_model(model, height, ModelObjectCutAttribute::KeepLower);
}
DynamicPrintConfig full_config;