Merge branch 'master' of https://github.com/Prusa-Development/PrusaSlicerPrivate into et_spe1784_binary_gcode

This commit is contained in:
enricoturri1966 2023-08-14 09:55:45 +02:00
commit d9d771c268
33 changed files with 3298 additions and 749 deletions

File diff suppressed because it is too large Load Diff

13
resources/icons/snap.svg Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<g>
<path fill="#808080" d="M9,15H7c-0.5522847,0-1-0.4477148-1-1V5c0-0.5522847,0.4477153-1,1-1h2c0.5522852,0,1,0.4477153,1,1v9
C10,14.5522852,9.5522852,15,9,15z"/>
</g>
<g>
<path fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-miterlimit="5" d="M1.5,11.5h2v-8c0-1.1045694,0.8954306-2,2-2h5c1.1045694,0,2,0.8954306,2,2v8h2"/>
</g>
<line fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" x1="1.5" y1="14.5" x2="14.5" y2="14.5"/>
</svg>

After

Width:  |  Height:  |  Size: 846 B

View File

@ -49,26 +49,12 @@ if (SLIC3R_GUI)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}")
find_package(wxWidgets 3.2 QUIET COMPONENTS base core adv html gl)
if (NOT wxWidgets_FOUND)
message(FATAL_ERROR "Could not find wxWidgets >= 3.2")
endif ()
if (NOT "${wxWidgets_USE_FILE}" STREQUAL "")
include(${wxWidgets_USE_FILE})
endif ()
else ()
find_package(wxWidgets 3.2 COMPONENTS html adv gl core base)
if (NOT wxWidgets_FOUND)
message(STATUS "Trying to find wxWidgets in CONFIG mode...")
find_package(wxWidgets 3.2 CONFIG REQUIRED COMPONENTS html adv gl core base)
slic3r_remap_configs(wx::wxhtml wx::wxadv wx::wxgl wx::wxcore wx::wxbase RelWithDebInfo Release)
else ()
if (NOT "${wxWidgets_USE_FILE}" STREQUAL "")
include(${wxWidgets_USE_FILE})
endif ()
endif ()
endif ()
find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl)
include(${wxWidgets_USE_FILE})
slic3r_remap_configs(wx::wxhtml wx::wxadv wx::wxgl wx::wxcore wx::wxbase RelWithDebInfo Release)
if(UNIX)
message(STATUS "wx-config path: ${wxWidgets_CONFIG_EXECUTABLE}")

View File

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

View File

@ -152,6 +152,7 @@ namespace ImGui
// const wchar_t MmuSegmentationMarker = 0x1F;
const wchar_t PlugMarker = 0x1C;
const wchar_t DowelMarker = 0x1D;
const wchar_t SnapMarker = 0x1E;
// Do not forget use following letters only in wstring
const wchar_t DocumentationButton = 0x2600;
const wchar_t DocumentationHoverButton = 0x2601;

View File

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

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

@ -0,0 +1,645 @@
#include "CutUtils.hpp"
#include "Geometry.hpp"
#include "libslic3r.h"
#include "Model.hpp"
#include "TriangleMeshSlicer.hpp"
#include "TriangleSelector.hpp"
#include "ObjectID.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_no_scaling_factor());
// add respect to mirroring
if (obj_instance->is_left_handed())
inst_trafo = inst_trafo * Transformation(scale_transform(Vec3d(-1, 1, 1)));
obj_instance->set_transformation(inst_trafo);
Vec3d rotation = Vec3d::Zero();
if (!flip && !place_on_cut) {
if ( i != src_instance_idx)
rotation[Z] = rot_z;
}
else {
Transform3d rotation_matrix = Transform3d::Identity();
if (flip)
rotation_matrix = rotation_transform(PI * Vec3d::UnitX());
if (place_on_cut)
rotation_matrix = rotation_matrix * Transformation(cut_matrix).get_rotation_matrix().inverse();
if (i != src_instance_idx)
rotation_matrix = rotation_transform(rot_z * Vec3d::UnitZ()) * rotation_matrix;
rotation = Transformation(rotation_matrix).get_rotation();
}
obj_instance->set_rotation(rotation);
}
}
Cut::Cut(const ModelObject* object, int instance, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes/*= ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepAsParts*/)
: m_instance(instance), m_cut_matrix(cut_matrix), m_attributes(attributes)
{
m_model = Model();
if (object)
m_model.add_object(*object);
}
void Cut::post_process(ModelObject* object, ModelObjectPtrs& cut_object_ptrs, bool keep, bool place_on_cut, bool flip)
{
if (!object) return;
if (keep && !object->volumes.empty()) {
reset_instance_transformation(object, m_instance, m_cut_matrix, place_on_cut, flip);
cut_object_ptrs.push_back(object);
}
else
m_model.objects.push_back(object); // will be deleted in m_model.clear_objects();
}
void Cut::post_process(ModelObject* upper, ModelObject* lower, ModelObjectPtrs& cut_object_ptrs)
{
post_process(upper, cut_object_ptrs,
m_attributes.has(ModelObjectCutAttribute::KeepUpper),
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
m_attributes.has(ModelObjectCutAttribute::FlipUpper));
post_process(lower, cut_object_ptrs,
m_attributes.has(ModelObjectCutAttribute::KeepLower),
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
m_attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || m_attributes.has(ModelObjectCutAttribute::FlipLower));
}
void Cut::finalize(const ModelObjectPtrs& objects)
{
//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_no_offset();
const Transformation cut_transformation = Transformation(m_cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
for (ModelVolume* volume : mo->volumes) {
volume->reset_extra_facets();
if (!volume->is_model_part()) {
if (volume->cut_info.is_processed)
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, m_attributes, upper, lower);
else
process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels);
}
else if (!volume->mesh().empty())
process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower);
}
// Post-process cut parts
if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && upper->volumes.empty()) {
m_model = Model();
m_model.objects.push_back(upper);
return m_model.objects;
}
ModelObjectPtrs cut_object_ptrs;
if (m_attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
reset_instance_transformation(upper, m_instance, m_cut_matrix);
cut_object_ptrs.push_back(upper);
}
else {
// Delete all modifiers which are not intersecting with solid parts bounding box
auto delete_extra_modifiers = [this](ModelObject* mo) {
if (!mo) return;
const BoundingBoxf3 obj_bb = mo->instance_bounding_box(m_instance);
const Transform3d inst_matrix = mo->instances[m_instance]->get_transformation().get_matrix();
for (int i = int(mo->volumes.size()) - 1; i >= 0; --i)
if (const ModelVolume* vol = mo->volumes[i];
!vol->is_model_part() && !vol->is_cut_connector()) {
auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix());
if (!obj_bb.intersects(bb))
mo->delete_volume(i);
}
};
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);
}
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);
// Add Dowel-connectors as separate objects to cut_object_ptrs
if (cut_connectors_obj.size() >= 3)
for (size_t id = 2; id < cut_connectors_obj.size(); id++)
cut_object_ptrs.push_back(cut_connectors_obj[id]);
}
// Now merge all model parts together:
merge_solid_parts_inside_object(cut_object_ptrs);
finalize(cut_object_ptrs);
return m_model.objects;
}
const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Transform3d& rotation_m, bool keep_as_parts/* = false*/)
{
ModelObject* cut_mo = m_model.objects.front();
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
cut_mo->clone_for_cut(&upper);
ModelObject* lower{ nullptr };
cut_mo->clone_for_cut(&lower);
const double groove_half_depth = 0.5 * double(groove.depth);
Model tmp_model_for_cut = Model();
Model tmp_model = Model();
tmp_model.add_object(*cut_mo);
ModelObject* tmp_object = tmp_model.objects.front();
auto add_volumes_from_cut = [](ModelObject* object, const ModelObjectCutAttribute attribute, const Model& tmp_model_for_cut) {
const auto& volumes = tmp_model_for_cut.objects.front()->volumes;
for (const ModelVolume* volume : volumes)
if (volume->is_model_part()) {
if ((attribute == ModelObjectCutAttribute::KeepUpper && volume->is_from_upper()) ||
(attribute != ModelObjectCutAttribute::KeepUpper && !volume->is_from_upper())) {
ModelVolume* new_vol = object->add_volume(*volume);
new_vol->reset_from_upper();
}
}
};
auto cut = [this, add_volumes_from_cut]
(ModelObject* object, const Transform3d& cut_matrix, const ModelObjectCutAttribute add_volumes_attribute, Model& tmp_model_for_cut) {
Cut cut(object, m_instance, cut_matrix);
tmp_model_for_cut = Model();
tmp_model_for_cut.add_object(*cut.perform_with_plane().front());
assert(!tmp_model_for_cut.objects.empty());
object->clear_volumes();
add_volumes_from_cut(object, add_volumes_attribute, tmp_model_for_cut);
reset_instance_transformation(object, m_instance);
};
// cut by upper plane
const Transform3d cut_matrix_upper = translation_transform(rotation_m * (groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
{
cut(tmp_object, cut_matrix_upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// cut by lower plane
const Transform3d cut_matrix_lower = translation_transform(rotation_m * (-groove_half_depth * Vec3d::UnitZ())) * m_cut_matrix;
{
cut(tmp_object, cut_matrix_lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
}
// cut middle part with 2 angles and add parts to related upper/lower objects
const double h_side_shift = 0.5 * double(groove.width + groove.depth / tan(groove.flaps_angle));
// cut by angle1 plane
{
const Transform3d cut_matrix_angle1 = translation_transform(rotation_m * (-h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
cut(tmp_object, cut_matrix_angle1, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// cut by angle2 plane
{
const Transform3d cut_matrix_angle2 = translation_transform(rotation_m * (h_side_shift * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(tmp_object, cut_matrix_angle2, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
add_volumes_from_cut(lower, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// apply tolerance to the middle part
{
const double h_groove_shift_tolerance = groove_half_depth - (double)groove.depth_tolerance;
const Transform3d cut_matrix_lower_tolerance = translation_transform(rotation_m * (-h_groove_shift_tolerance * Vec3d::UnitZ())) * m_cut_matrix;
cut(tmp_object, cut_matrix_lower_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
const double h_side_shift_tolerance = h_side_shift - 0.5 * double(groove.width_tolerance);
const Transform3d cut_matrix_angle1_tolerance = translation_transform(rotation_m * (-h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, -groove.flaps_angle, -groove.angle));
cut(tmp_object, cut_matrix_angle1_tolerance, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
const Transform3d cut_matrix_angle2_tolerance = translation_transform(rotation_m * (h_side_shift_tolerance * Vec3d::UnitX())) * m_cut_matrix * rotation_transform(Vec3d(0, groove.flaps_angle, groove.angle));
cut(tmp_object, cut_matrix_angle2_tolerance, ModelObjectCutAttribute::KeepUpper, tmp_model_for_cut);
}
// this part can be added to the upper object now
add_volumes_from_cut(upper, ModelObjectCutAttribute::KeepLower, tmp_model_for_cut);
ModelObjectPtrs cut_object_ptrs;
if (keep_as_parts) {
// add volumes from lower object to the upper, but mark them as a lower
const auto& volumes = lower->volumes;
for (const ModelVolume* volume : volumes) {
ModelVolume* new_vol = upper->add_volume(*volume);
new_vol->cut_info.is_from_upper = false;
}
// add modifiers
for (const ModelVolume* volume : cut_mo->volumes)
if (!volume->is_model_part())
upper->add_volume(*volume);
cut_object_ptrs.push_back(upper);
// add lower object to the cut_object_ptrs just to correct delete it from the Model destructor and avoid memory leaks
cut_object_ptrs.push_back(lower);
}
else {
// add modifiers if object has any
for (const ModelVolume* volume : cut_mo->volumes)
if (!volume->is_model_part()) {
distribute_modifiers_from_object(cut_mo, m_instance, upper, lower);
break;
}
assert(!upper->volumes.empty() && !lower->volumes.empty());
// Add Upper and Lower parts to cut_object_ptrs
post_process(upper, lower, cut_object_ptrs);
// Now merge all model parts together:
merge_solid_parts_inside_object(cut_object_ptrs);
}
finalize(cut_object_ptrs);
return m_model.objects;
}
} // namespace Slic3r

View File

@ -0,0 +1,66 @@
#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);
}; // namespace Cut
} // namespace Slic3r
#endif /* slic3r_CutUtils_hpp_ */

View File

@ -146,67 +146,68 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
std::vector<float> angles_for_curvature(points.size());
std::vector<float> distances_for_curvature(points.size());
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
ExtendedPoint &a = points[point_idx];
ExtendedPoint &prev = points[point_idx > 0 ? point_idx - 1 : point_idx];
size_t prev = prev_idx_modulo(point_idx, points.size());
size_t next = next_idx_modulo(point_idx, points.size());
int prev_point_idx = point_idx;
while (prev_point_idx > 0) {
prev_point_idx--;
if ((a.position - points[prev_point_idx].position).squaredNorm() > EPSILON) {
break;
}
int iter_limit = points.size();
while ((a.position - points[prev].position).squaredNorm() < 1 && iter_limit > 0) {
prev = prev_idx_modulo(prev, points.size());
iter_limit--;
}
int next_point_index = point_idx;
while (next_point_index < int(points.size()) - 1) {
next_point_index++;
if ((a.position - points[next_point_index].position).squaredNorm() > EPSILON) {
break;
}
while ((a.position - points[next].position).squaredNorm() < 1 && iter_limit > 0) {
next = next_idx_modulo(next, points.size());
iter_limit--;
}
distances_for_curvature[point_idx] = (prev.position - a.position).norm();
if (prev_point_idx != point_idx && next_point_index != point_idx) {
float alfa = angle(a.position - points[prev_point_idx].position, points[next_point_index].position - a.position);
angles_for_curvature[point_idx] = alfa;
} // else keep zero
distances_for_curvature[point_idx] = (points[prev].position - a.position).norm();
float alfa = angle(a.position - points[prev].position, points[next].position - a.position);
angles_for_curvature[point_idx] = alfa;
}
for (float window_size : {3.0f, 9.0f, 16.0f}) {
size_t tail_point = 0;
float tail_window_acc = 0;
float tail_angle_acc = 0;
if (std::accumulate(distances_for_curvature.begin(), distances_for_curvature.end(), 0) > EPSILON)
for (float window_size : {3.0f, 9.0f, 16.0f}) {
size_t tail_point = 0;
float tail_window_acc = 0;
float tail_angle_acc = 0;
size_t head_point = 0;
float head_window_acc = 0;
float head_angle_acc = 0;
size_t head_point = 0;
float head_window_acc = 0;
float head_angle_acc = 0;
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
if (point_idx > 0) {
tail_window_acc += distances_for_curvature[point_idx - 1];
tail_angle_acc += angles_for_curvature[point_idx - 1];
head_window_acc -= distances_for_curvature[point_idx - 1];
head_angle_acc -= angles_for_curvature[point_idx - 1];
}
while (tail_window_acc > window_size * 0.5 && int(tail_point) < point_idx) {
tail_window_acc -= distances_for_curvature[tail_point];
tail_angle_acc -= angles_for_curvature[tail_point];
tail_point++;
}
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
if (point_idx == 0) {
while (tail_window_acc < window_size * 0.5) {
tail_window_acc += distances_for_curvature[tail_point];
tail_angle_acc += angles_for_curvature[tail_point];
tail_point = prev_idx_modulo(tail_point, points.size());
}
}
while (tail_window_acc - distances_for_curvature[next_idx_modulo(tail_point, points.size())] > window_size * 0.5) {
tail_point = next_idx_modulo(tail_point, points.size());
tail_window_acc -= distances_for_curvature[tail_point];
tail_angle_acc -= angles_for_curvature[tail_point];
}
while (head_window_acc < window_size * 0.5 && int(head_point) < int(points.size()) - 1) {
head_window_acc += distances_for_curvature[head_point];
head_angle_acc += angles_for_curvature[head_point];
head_point++;
}
while (head_window_acc < window_size * 0.5) {
head_point = next_idx_modulo(head_point, points.size());
head_window_acc += distances_for_curvature[head_point];
head_angle_acc += angles_for_curvature[head_point];
}
float curvature = (tail_angle_acc + head_angle_acc) / (tail_window_acc + head_window_acc);
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
points[point_idx].curvature = curvature;
float curvature = (tail_angle_acc + head_angle_acc) / window_size;
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
points[point_idx].curvature = curvature;
}
tail_window_acc += distances_for_curvature[point_idx];
tail_angle_acc += angles_for_curvature[point_idx];
head_window_acc -= distances_for_curvature[point_idx];
head_angle_acc -= angles_for_curvature[point_idx];
}
}
}
return points;
}

View File

@ -1280,64 +1280,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::Prism)
connector_mesh = its_make_cylinder(1.0, 1.0, (2 * PI / sectorCount));
else if (connector_attributes.type == CutConnectorType::Plug)
connector_mesh = its_make_frustum(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& new_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);
// Transform the new modifier to be aligned inside the instance
new_volume->set_transformation(translation_transform(connector.pos) * connector.rotation_m *
scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast<double>()));
new_volume->cut_info = { connector.attribs.type, connector.radius_tolerance, connector.height_tolerance };
new_volume->name = new_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();
@ -1390,297 +1332,6 @@ void ModelVolume::reset_extra_facets()
this->mmu_segmentation_facets.reset();
}
void ModelVolume::apply_tolerance()
{
assert(cut_info.is_connector);
if (!cut_info.is_processed)
return;
Vec3d sf = get_scaling_factor();
// make a "hole" wider
sf[X] += double(cut_info.radius_tolerance);
sf[Y] += double(cut_info.radius_tolerance);
// make a "hole" dipper
sf[Z] += double(cut_info.height_tolerance);
set_scaling_factor(sf);
// correct offset in respect to the new depth
Vec3d rot_norm = Geometry::rotation_transform(get_rotation()) * Vec3d::UnitZ();
if (rot_norm.norm() != 0.0)
rot_norm.normalize();
double z_offset = 0.5 * static_cast<double>(cut_info.height_tolerance);
if (cut_info.connector_type == CutConnectorType::Plug)
z_offset -= 0.05; // add small Z offset to better preview
set_offset(get_offset() + rot_norm * z_offset);
}
static void add_cut_volume(TriangleMesh& mesh, ModelObject* object, const ModelVolume* src_volume, const Transform3d& cut_matrix, const std::string& suffix = {}, ModelVolumeType type = ModelVolumeType::MODEL_PART)
{
if (mesh.empty())
return;
mesh.transform(cut_matrix);
ModelVolume* vol = object->add_volume(mesh);
vol->set_type(type);
vol->name = src_volume->name + suffix;
// Don't copy the config's ID.
vol->config.assign_config(src_volume->config);
assert(vol->config.id().valid());
assert(vol->config.id() != src_volume->config.id());
vol->set_material(src_volume->material_id(), *src_volume->material());
vol->cut_info = src_volume->cut_info;
}
void ModelObject::process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels)
{
assert(volume->cut_info.is_connector);
volume->cut_info.set_processed();
const auto volume_matrix = volume->get_matrix();
// ! Don't apply instance transformation for the conntectors.
// This transformation is already there
if (volume->cut_info.connector_type != CutConnectorType::Dowel) {
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
ModelVolume* vol = upper->add_volume(*volume);
vol->set_transformation(volume_matrix);
vol->apply_tolerance();
}
if (attributes.has(ModelObjectCutAttribute::KeepLower)) {
ModelVolume* vol = lower->add_volume(*volume);
vol->set_transformation(volume_matrix);
// for lower part change type of connector from NEGATIVE_VOLUME to MODEL_PART if this connector is a plug
vol->set_type(ModelVolumeType::MODEL_PART);
}
}
else {
if (attributes.has(ModelObjectCutAttribute::CreateDowels)) {
ModelObject* dowel{ nullptr };
// Clone the object to duplicate instances, materials etc.
clone_for_cut(&dowel);
// add one more solid part same as connector if this connector is a dowel
ModelVolume* vol = dowel->add_volume(*volume);
vol->set_type(ModelVolumeType::MODEL_PART);
// But discard rotation and Z-offset for this volume
vol->set_rotation(Vec3d::Zero());
vol->set_offset(Z, 0.0);
dowels.push_back(dowel);
}
// Cut the dowel
volume->apply_tolerance();
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
process_volume_cut(volume, Transform3d::Identity(), cut_matrix, attributes, upper_mesh, lower_mesh);
// add small Z offset to better preview
upper_mesh.translate((-0.05 * Vec3d::UnitZ()).cast<float>());
lower_mesh.translate((0.05 * Vec3d::UnitZ()).cast<float>());
// Add cut parts to the related objects
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A", volume->type());
add_cut_volume(lower_mesh, lower, volume, cut_matrix, "_B", volume->type());
}
}
void ModelObject::process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
{
const auto volume_matrix = instance_matrix * volume->get_matrix();
// Modifiers are not cut, but we still need to add the instance transformation
// to the modifier volume transformation to preserve their shape properly.
volume->set_transformation(Geometry::Transformation(volume_matrix));
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
upper->add_volume(*volume);
return;
}
// Some logic for the negative volumes/connectors. Add only needed modifiers
auto bb = volume->mesh().transformed_bounding_box(inverse_cut_matrix * volume_matrix);
bool is_crossed_by_cut = bb.min[Z] <= 0 && bb.max[Z] >= 0;
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && (bb.min[Z] >= 0 || is_crossed_by_cut))
upper->add_volume(*volume);
if (attributes.has(ModelObjectCutAttribute::KeepLower) && (bb.max[Z] <= 0 || is_crossed_by_cut))
lower->add_volume(*volume);
}
void ModelObject::process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh)
{
const auto volume_matrix = volume->get_matrix();
using namespace Geometry;
const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d invert_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1 * cut_transformation.get_offset());
// Transform the mesh by the combined transformation matrix.
// Flip the triangles in case the composite transformation is left handed.
TriangleMesh mesh(volume->mesh());
mesh.transform(invert_cut_matrix * instance_matrix * volume_matrix, true);
indexed_triangle_set upper_its, lower_its;
cut_mesh(mesh.its, 0.0f, &upper_its, &lower_its);
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
upper_mesh = TriangleMesh(upper_its);
if (attributes.has(ModelObjectCutAttribute::KeepLower))
lower_mesh = TriangleMesh(lower_its);
}
void ModelObject::process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
{
// Perform cut
TriangleMesh upper_mesh, lower_mesh;
process_volume_cut(volume, instance_matrix, cut_matrix, attributes, upper_mesh, lower_mesh);
// Add required cut parts to the objects
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
return;
}
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
}
void ModelObject::reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut/* = false*/, bool flip/* = false*/)
{
using namespace Geometry;
// Reset instance transformation except offset and Z-rotation
for (size_t i = 0; i < object->instances.size(); ++i) {
auto& obj_instance = object->instances[i];
const double rot_z = obj_instance->get_rotation().z();
Transformation inst_trafo = Transformation(obj_instance->get_transformation().get_matrix_no_scaling_factor());
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);
}
}
ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
{
if (!attributes.has(ModelObjectCutAttribute::KeepUpper) && !attributes.has(ModelObjectCutAttribute::KeepLower))
return {};
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
// Clone the object to duplicate instances, materials etc.
ModelObject* upper{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
clone_for_cut(&upper);
ModelObject* lower{ nullptr };
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !attributes.has(ModelObjectCutAttribute::KeepAsParts))
clone_for_cut(&lower);
std::vector<ModelObject*> dowels;
using namespace Geometry;
// Because transformations are going to be applied to meshes directly,
// we reset transformation of all instances and volumes,
// except for translation and Z-rotation on instances, which are preserved
// in the transformation matrix and not applied to the mesh transform.
// const auto instance_matrix = instances[instance]->get_matrix(true);
const auto instance_matrix = instances[instance]->get_transformation().get_matrix_no_offset();
const Transformation cut_transformation = Transformation(cut_matrix);
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
for (ModelVolume* volume : volumes) {
volume->reset_extra_facets();
if (!volume->is_model_part()) {
if (volume->cut_info.is_processed)
process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower);
else
process_connector_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, dowels);
}
else if (!volume->mesh().empty())
process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower);
}
// Post-process cut parts
ModelObjectPtrs res;
if (attributes.has(ModelObjectCutAttribute::KeepAsParts) && !upper->volumes.empty()) {
reset_instance_transformation(upper, instance, cut_matrix);
res.push_back(upper);
}
else {
if (attributes.has(ModelObjectCutAttribute::KeepUpper) && !upper->volumes.empty()) {
reset_instance_transformation(upper, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutUpper),
attributes.has(ModelObjectCutAttribute::FlipUpper));
res.push_back(upper);
}
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower->volumes.empty()) {
reset_instance_transformation(lower, instance, cut_matrix,
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower),
attributes.has(ModelObjectCutAttribute::PlaceOnCutLower) || attributes.has(ModelObjectCutAttribute::FlipLower));
res.push_back(lower);
}
if (attributes.has(ModelObjectCutAttribute::CreateDowels) && !dowels.empty()) {
for (auto dowel : dowels) {
reset_instance_transformation(dowel, instance, Transform3d::Identity());
dowel->name += "-Dowel-" + dowel->volumes[0]->name;
res.push_back(dowel);
}
}
}
BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
return res;
}
/// <summary>
/// Compare TriangleMeshes by Bounding boxes (mainly for sort)

View File

@ -224,6 +224,7 @@ private:
enum class CutConnectorType : int {
Plug
, Dowel
, Snap
, Undef
};
@ -316,10 +317,6 @@ enum class ModelVolumeType : int {
SUPPORT_ENFORCER,
};
enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, KeepAsParts, FlipUpper, FlipLower, PlaceOnCutUpper, PlaceOnCutLower, CreateDowels, InvalidateCutInfo };
using ModelObjectCutAttributes = enum_bitmask<ModelObjectCutAttribute>;
ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute);
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
@ -461,29 +458,12 @@ public:
size_t materials_count() const;
size_t facets_count() const;
size_t parts_count() 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 clone_for_cut(ModelObject **obj);
private:
void process_connector_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
std::vector<ModelObject*>& dowels);
void process_modifier_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& inverse_cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
void process_volume_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, TriangleMesh& upper_mesh, TriangleMesh& lower_mesh);
void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower);
public:
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx, const Transform3d& cut_matrix,
bool place_on_cut = false, bool flip = false);
ModelObjectPtrs cut(size_t instance, const Transform3d&cut_matrix, ModelObjectCutAttributes attributes);
void split(ModelObjectPtrs*new_objects);
void merge();
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
@ -777,6 +757,7 @@ public:
// It contains information about connetors
struct CutInfo
{
bool is_from_upper{ true };
bool is_connector{ false };
bool is_processed{ true };
CutConnectorType connector_type{ CutConnectorType::Plug };
@ -794,6 +775,7 @@ public:
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);
@ -801,6 +783,9 @@ public:
};
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(); }
@ -846,7 +831,6 @@ public:
bool is_the_only_one_part() const; // behave like an object
t_model_material_id material_id() const { return m_material_id; }
void reset_extra_facets();
void apply_tolerance();
void set_material_id(t_model_material_id material_id);
ModelMaterial* material() const;
void set_material(t_model_material_id material_id, const ModelMaterial &material);

View File

@ -678,7 +678,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
void PresetBundle::export_selections(AppConfig &config)
{
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() >= 1);
assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias);
// #ysFIXME_delete_after_test !All filament selections are always saved in extruder_filaments (for MM and SM printers),
// so there is no need to control a correspondence between filaments and extruders_filaments
//assert(this->printers.get_edited_preset().printer_technology() != ptFFF || extruders_filaments.size() > 1 || filaments.get_selected_preset().alias == extruders_filaments.front().get_selected_preset()->alias);
config.clear_section("presets");
config.set("presets", "print", prints.get_selected_preset_name());
config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name());

View File

@ -1539,21 +1539,33 @@ void PrintObject::discover_vertical_shells()
// Finally expand the infill a bit to remove tiny gaps between solid infill and the other regions.
narrow_sparse_infill_region_radius - tiny_overlap_radius, ClipperLib::jtSquare);
Polygons object_volume;
Polygons internal_volume;
{
Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{};
Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ?
to_polygons(m_layers[idx_layer + 1]->lslices) :
Polygons{};
internal_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
internal_volume = closing(polygonsInternal, SCALED_EPSILON);
}
// The opening operation may cause scattered tiny drops on the smooth parts of the model, filter them out
// The regularization operation may cause scattered tiny drops on the smooth parts of the model, filter them out
// If the region checks both following conditions, it is removed:
// 1. the area is very small,
// OR the area is quite small and it is fully wrapped in model (not visible)
// the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy
// 2. the area does not fully cover an internal polygon
// This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small
regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(),
[&min_perimeter_infill_spacing, &internal_volume](const ExPolygon &p) {
return p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
diff(to_polygons(p), internal_volume).empty());
[&internal_volume, &min_perimeter_infill_spacing,
&object_volume](const ExPolygon &p) {
return (p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
diff(to_polygons(p), object_volume).empty())) &&
diff(internal_volume,
expand(to_polygons(p), min_perimeter_infill_spacing))
.size() >= internal_volume.size();
}),
regularized_shell.end());
}

View File

@ -1262,6 +1262,127 @@ indexed_triangle_set its_make_frustum_dowel(double radius, double h, int sectorC
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

@ -321,6 +321,7 @@ 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

@ -2757,6 +2757,11 @@ bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, doubl
bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
{
// Save previously value of the tick before the call a Dialog from get_... functions,
// otherwise a background process can change ticks values and current iterator wouldn't be valid for the moment of a Dialog close
// and PS will crash (see https://github.com/prusa3d/PrusaSlicer/issues/10941)
TickCode changed_tick = *it;
std::string edited_value;
if (it->type == ColorChange)
edited_value = get_new_color(it->color);
@ -2768,7 +2773,10 @@ bool TickCodeInfo::edit_tick(std::set<TickCode>::iterator it, double print_z)
if (edited_value.empty())
return false;
TickCode changed_tick = *it;
// Update iterator. For this moment its value can be invalid
if (it = ticks.find(changed_tick); it == ticks.end())
return false;
if (it->type == ColorChange) {
if (it->color == edited_value)
return false;

View File

@ -588,6 +588,8 @@ bool TextCtrl::value_was_changed()
case coFloatOrPercent:
case coFloatsOrPercents:
return boost::any_cast<std::string>(m_value) != boost::any_cast<std::string>(val);
case coPoints:
return boost::any_cast<std::vector<Vec2d>>(m_value) != boost::any_cast<std::vector<Vec2d>>(val);
default:
return true;
}

View File

@ -2689,6 +2689,9 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
if (curr_gizmo != nullptr)
curr_gizmo->unregister_raycasters_for_picking();
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo);
if (curr_gizmo != nullptr && !m_selection.is_empty())
curr_gizmo->register_raycasters_for_picking();
m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo);
if (curr_gizmo != nullptr && !m_selection.is_empty())
curr_gizmo->register_raycasters_for_picking();
@ -3597,11 +3600,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
update_sequential_clearance(true);
}
}
else if (evt.LeftUp() &&
m_gizmos.get_current_type() == GLGizmosManager::EType::Scale &&
m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) {
wxGetApp().obj_list()->selection_changed();
}
return;
}
@ -4071,7 +4069,7 @@ void GLCanvas3D::do_move(const std::string& snapshot_type)
model_object->invalidate_bounding_box();
}
}
else if (v->is_wipe_tower)
else if (m_selection.is_wipe_tower() && v->is_wipe_tower)
// Move a wipe tower proxy.
wipe_tower_origin = v->get_volume_offset();
}
@ -5749,6 +5747,7 @@ void GLCanvas3D::_picking_pass()
break;
}
case SceneRaycaster::EType::Gizmo:
case SceneRaycaster::EType::FallbackGizmo:
{
const Size& cnv_size = get_canvas_size();
const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() &&
@ -5781,6 +5780,7 @@ void GLCanvas3D::_picking_pass()
{
case SceneRaycaster::EType::Bed: { object_type = "Bed"; break; }
case SceneRaycaster::EType::Gizmo: { object_type = "Gizmo element"; break; }
case SceneRaycaster::EType::FallbackGizmo: { object_type = "Gizmo2 element"; break; }
case SceneRaycaster::EType::Volume:
{
if (m_volumes.volumes[hit.raycaster_id]->is_wipe_tower)
@ -5835,6 +5835,8 @@ void GLCanvas3D::_picking_pass()
add_strings_row_to_table("Volumes", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
sprintf(buf, "%d (%d)", (int)m_scene_raycaster.gizmos_count(), (int)m_scene_raycaster.active_gizmos_count());
add_strings_row_to_table("Gizmo elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
sprintf(buf, "%d (%d)", (int)m_scene_raycaster.fallback_gizmos_count(), (int)m_scene_raycaster.active_fallback_gizmos_count());
add_strings_row_to_table("Gizmo2 elements", ImGuiWrapper::COL_ORANGE_LIGHT, std::string(buf), ImGui::GetStyleColorVec4(ImGuiCol_Text));
ImGui::EndTable();
}
@ -5852,6 +5854,20 @@ void GLCanvas3D::_picking_pass()
}
}
std::vector<std::shared_ptr<SceneRaycasterItem>>* gizmo2_raycasters = m_scene_raycaster.get_raycasters(SceneRaycaster::EType::FallbackGizmo);
if (gizmo2_raycasters != nullptr && !gizmo2_raycasters->empty()) {
ImGui::Separator();
imgui.text("Gizmo2 raycasters IDs:");
if (ImGui::BeginTable("Gizmo2Raycasters", 3)) {
for (size_t i = 0; i < gizmo2_raycasters->size(); ++i) {
add_strings_row_to_table(std::to_string(i), ImGuiWrapper::COL_ORANGE_LIGHT,
std::to_string(SceneRaycaster::decode_id(SceneRaycaster::EType::FallbackGizmo, (*gizmo2_raycasters)[i]->get_id())), ImGui::GetStyleColorVec4(ImGuiCol_Text),
to_string(Geometry::Transformation((*gizmo2_raycasters)[i]->get_transform()).get_offset()), ImGui::GetStyleColorVec4(ImGuiCol_Text));
}
ImGui::EndTable();
}
}
imgui.end();
#endif // ENABLE_RAYCAST_PICKING_DEBUG
}

View File

@ -503,6 +503,11 @@ void ObjectManipulation::Show(const bool show)
}
m_word_local_combo->Show(show_world_local_combo);
m_empty_str->Show(!show_world_local_combo);
m_skew_label->Show(m_show_skew);
m_reset_skew_button->Show(m_show_skew);
m_parent->Layout();
}
}
@ -795,15 +800,22 @@ void ObjectManipulation::update_reset_buttons_visibility()
m_mirror_warning_bitmap->SetBitmap(show_mirror ? m_manifold_warning_bmp.bmp() : wxNullBitmap);
m_mirror_warning_bitmap->SetMinSize(show_mirror ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0));
m_mirror_warning_bitmap->SetToolTip(show_mirror ? _L("Left handed") : "");
m_reset_skew_button->Show(show_skew);
m_skew_label->Show(show_skew);
// Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
Sidebar& panel = wxGetApp().sidebar();
if (!panel.IsFrozen()) {
panel.Freeze();
panel.Layout();
panel.Thaw();
if (m_show_skew == show_skew)
get_sizer()->Layout();
else {
// Call sidebar layout only if it's really needed,
// it means, when we show/hide additional line for skew information
m_show_skew = show_skew;
m_reset_skew_button->Show(m_show_skew);
m_skew_label->Show(m_show_skew);
// Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time
Sidebar& panel = wxGetApp().sidebar();
if (!panel.IsFrozen()) {
panel.Freeze();
panel.Layout();
panel.Thaw();
}
}
});
}

View File

@ -169,6 +169,7 @@ private:
bool m_is_enabled { true };
bool m_is_enabled_size_and_scale { true };
bool m_show_skew { false };
public:
ObjectManipulation(wxWindow* parent);

View File

@ -134,7 +134,12 @@ GalleryDialog::GalleryDialog(wxWindow* parent) :
}
GalleryDialog::~GalleryDialog()
{
{
// From wxWidgets docs:
// The method void wxListCtrl::SetImageList(wxImageList* imageList, int which)
// does not take ownership of the image list, you have to delete it yourself.
if (m_image_list)
delete m_image_list;
}
int GalleryDialog::show(bool show_from_menu)

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,14 @@
#include "slic3r/GUI/I18N.hpp"
#include "libslic3r/TriangleMesh.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 {
@ -29,6 +31,9 @@ class GLGizmoCut3D : public GLGizmoBase
Y,
Z,
CutPlane,
CutPlaneZRotation,
CutPlaneXMove,
CutPlaneYMove,
Count,
};
@ -54,6 +59,7 @@ class GLGizmoCut3D : public GLGizmoBase
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 };
@ -78,6 +84,7 @@ class GLGizmoCut3D : public GLGizmoBase
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;
@ -111,6 +118,16 @@ class GLGizmoCut3D : public GLGizmoBase
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 };
@ -127,7 +144,6 @@ class GLGizmoCut3D : public GLGizmoBase
float m_contour_width{ 0.4f };
float m_cut_plane_radius_koef{ 1.5f };
bool m_is_contour_changed{ false };
float m_shortcut_label_width{ -1.f };
mutable std::vector<bool> m_selected; // which pins are currently selected
@ -139,10 +155,14 @@ class GLGizmoCut3D : public GLGizmoBase
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 {
@ -161,6 +181,8 @@ class GLGizmoCut3D : public GLGizmoBase
const std::vector<Part>& parts() const { return m_parts; }
const std::vector<size_t>* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
std::vector<Cut::Part> get_cut_parts();
private:
Model m_model;
int m_instance_idx;
@ -171,6 +193,8 @@ class GLGizmoCut3D : public GLGizmoBase
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;
@ -180,7 +204,8 @@ class GLGizmoCut3D : public GLGizmoBase
enum class CutMode {
cutPlanar
, cutGrig
, cutTongueAndGroove
//, cutGrig
//,cutRadial
//,cutModular
};
@ -190,7 +215,7 @@ class GLGizmoCut3D : public GLGizmoBase
, Manual
};
// std::vector<std::string> m_modes;
std::vector<std::string> m_modes;
size_t m_mode{ size_t(CutMode::cutPlanar) };
std::vector<std::string> m_connector_modes;
@ -215,7 +240,7 @@ public:
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_disabled_contour = true);
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; }
@ -249,8 +274,8 @@ protected:
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_z(const GLGizmoBase::UpdateData &data);
void dragging_grabber_xy(const GLGizmoBase::UpdateData &data);
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;
@ -275,6 +300,8 @@ protected:
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);
void render_cut_plane_input_window(CutConnectors &connectors);
void init_input_window_data(CutConnectors &connectors);
void render_input_window_warning() const;
@ -290,6 +317,8 @@ protected:
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; }
@ -301,10 +330,12 @@ protected:
Transform3d get_cut_matrix(const Selection& selection);
private:
void set_center(const Vec3d& center, bool update_tbb = false);
bool render_combo(const std::string& label, const std::vector<std::string>& lines, int& selection_idx);
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);
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;
@ -314,16 +345,18 @@ private:
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);
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);
@ -339,6 +372,13 @@ private:
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

@ -99,6 +99,12 @@ void GLGizmoScale3D::enable_ununiversal_scale(bool enable)
m_grabbers[i].enabled = enable;
}
void GLGizmoScale3D::on_set_state()
{
if (m_state == On)
wxGetApp().obj_list()->selection_changed();
}
void GLGizmoScale3D::data_changed(bool is_serializing) {
set_scale(Vec3d::Ones());
}

View File

@ -74,6 +74,8 @@ protected:
virtual void on_register_raycasters_for_picking() override;
virtual void on_unregister_raycasters_for_picking() override;
void on_set_state() override;
private:
void render_grabbers_connection(unsigned int id_1, unsigned int id_2, const ColorRGBA& color);

View File

@ -67,6 +67,7 @@ static const std::map<const wchar_t, std::string> font_icons = {
{ImGui::InfoMarkerSmall , "notification_info" },
{ImGui::PlugMarker , "plug" },
{ImGui::DowelMarker , "dowel" },
{ImGui::SnapMarker , "snap" },
};
static const std::map<const wchar_t, std::string> font_icons_large = {
@ -639,6 +640,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);

View File

@ -49,6 +49,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();
@ -80,6 +85,7 @@ public:
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);

View File

@ -465,14 +465,17 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
bool MeshRaycaster::is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const
bool MeshRaycaster::intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const
{
point = trafo.inverse() * point;
Transform3d trafo_inv = trafo.inverse();
Vec3d to = trafo_inv * (point + direction);
point = trafo_inv * point;
direction = (to-point).normalized();
std::vector<AABBMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
std::vector<AABBMesh::hit_result> neg_hits = m_emesh.query_ray_hits(point, -direction);
return !hits.empty() && !neg_hits.empty();
return !hits.empty() || !neg_hits.empty();
}

View File

@ -187,7 +187,9 @@ public:
const AABBMesh &get_aabb_mesh() const { return m_emesh; }
bool is_valid_intersection(Vec3d point, Vec3d direction, const Transform3d& trafo) const;
// Given a point and direction in world coords, returns whether the respective line
// intersects the mesh if it is transformed into world by trafo.
bool intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const;
// Given a vector of points in woorld coordinates, this returns vector
// of indices of points that are visible (i.e. not cut by clipping plane

View File

@ -1280,9 +1280,11 @@ void Sidebar::show_info_sizer()
{
Selection& selection = wxGetApp().plater()->canvas3D()->get_selection();
ModelObjectPtrs objects = p->plater->model().objects;
int obj_idx = selection.get_object_idx();
const int obj_idx = selection.get_object_idx();
const int inst_idx = selection.get_instance_idx();
if (m_mode < comExpert || objects.empty() || obj_idx < 0 || int(objects.size()) <= obj_idx ||
inst_idx < 0 || int(objects[obj_idx]->instances.size()) <= inst_idx ||
objects[obj_idx]->volumes.empty() || // hack to avoid crash when deleting the last object on the bed
(selection.is_single_full_object() && objects[obj_idx]->instances.size()> 1) ||
!(selection.is_single_full_instance() || selection.is_single_volume())) {
@ -1292,9 +1294,6 @@ void Sidebar::show_info_sizer()
const ModelObject* model_object = objects[obj_idx];
int inst_idx = selection.get_instance_idx();
assert(inst_idx >= 0);
bool imperial_units = wxGetApp().app_config->get_bool("use_inches");
double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f;
@ -6417,20 +6416,7 @@ void Plater::toggle_layers_editing(bool enable)
canvas3D()->force_main_toolbar_left_action(canvas3D()->get_main_toolbar_item_id("layersediting"));
}
void Plater::cut(size_t obj_idx, size_t instance_idx, const Transform3d& cut_matrix, ModelObjectCutAttributes attributes)
{
wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds");
auto* object = p->model.objects[obj_idx];
wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds");
wxBusyCursor wait;
const auto new_objects = object->cut(instance_idx, cut_matrix, attributes);
cut(obj_idx, new_objects);
}
void Plater::cut(size_t obj_idx, const ModelObjectPtrs& new_objects)
void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& new_objects)
{
model().delete_object(obj_idx);
sidebar().obj_list()->delete_object_from_list(obj_idx);
@ -7232,7 +7218,9 @@ void Plater::force_filament_cb_update()
// Update preset comboboxes on sidebar and filaments tab
p->sidebar->update_presets(Preset::TYPE_FILAMENT);
wxGetApp().get_tab(Preset::TYPE_FILAMENT)->select_preset(wxGetApp().preset_bundle->filaments.get_selected_preset_name());
TabFilament* tab = dynamic_cast<TabFilament*>(wxGetApp().get_tab(Preset::TYPE_FILAMENT));
tab->select_preset(wxGetApp().preset_bundle->extruders_filaments[tab->get_active_extruder()].get_selected_preset_name());
}
void Plater::force_print_bed_update()

View File

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

View File

@ -40,6 +40,7 @@ std::shared_ptr<SceneRaycasterItem> SceneRaycaster::add_raycaster(EType type, in
case EType::Bed: { return m_bed.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
case EType::Volume: { return m_volumes.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
case EType::FallbackGizmo: { return m_fallback_gizmos.emplace_back(std::make_shared<SceneRaycasterItem>(encode_id(type, id), raycaster, trafo, use_back_faces)); }
default: { assert(false); return nullptr; }
};
}
@ -62,6 +63,7 @@ void SceneRaycaster::remove_raycasters(EType type)
case EType::Bed: { m_bed.clear(); break; }
case EType::Volume: { m_volumes.clear(); break; }
case EType::Gizmo: { m_gizmos.clear(); break; }
case EType::FallbackGizmo: { m_fallback_gizmos.clear(); break; }
default: { break; }
};
}
@ -86,6 +88,12 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr<SceneRaycasterItem> item)
return;
}
}
for (auto it = m_fallback_gizmos.begin(); it != m_fallback_gizmos.end(); ++it) {
if (*it == item) {
m_fallback_gizmos.erase(it);
return;
}
}
}
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const
@ -174,6 +182,9 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
if (!m_gizmos.empty())
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
if (!m_fallback_gizmos.empty() && !ret.is_valid())
test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
if (!m_gizmos_on_top || !ret.is_valid()) {
if (camera.is_looking_downward() && !m_bed.empty())
test_raycasters(EType::Bed, mouse_pos, camera, ret);
@ -241,6 +252,14 @@ size_t SceneRaycaster::active_gizmos_count() const {
}
return count;
}
size_t SceneRaycaster::active_fallback_gizmos_count() const {
size_t count = 0;
for (const auto& g : m_fallback_gizmos) {
if (g->is_active())
++count;
}
return count;
}
#endif // ENABLE_RAYCAST_PICKING_DEBUG
std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_raycasters(EType type)
@ -251,6 +270,7 @@ std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_raycasters
case EType::Bed: { ret = &m_bed; break; }
case EType::Volume: { ret = &m_volumes; break; }
case EType::Gizmo: { ret = &m_gizmos; break; }
case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; }
default: { break; }
}
assert(ret != nullptr);
@ -265,6 +285,7 @@ const std::vector<std::shared_ptr<SceneRaycasterItem>>* SceneRaycaster::get_rayc
case EType::Bed: { ret = &m_bed; break; }
case EType::Volume: { ret = &m_volumes; break; }
case EType::Gizmo: { ret = &m_gizmos; break; }
case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; }
default: { break; }
}
assert(ret != nullptr);
@ -278,6 +299,7 @@ int SceneRaycaster::base_id(EType type)
case EType::Bed: { return int(EIdBase::Bed); }
case EType::Volume: { return int(EIdBase::Volume); }
case EType::Gizmo: { return int(EIdBase::Gizmo); }
case EType::FallbackGizmo: { return int(EIdBase::FallbackGizmo); }
default: { break; }
};

View File

@ -42,14 +42,16 @@ public:
None,
Bed,
Volume,
Gizmo
Gizmo,
FallbackGizmo // Is used for gizmo grabbers which will be hit after all grabbers of Gizmo type
};
enum class EIdBase
{
Bed = 0,
Volume = 1000,
Gizmo = 1000000
Gizmo = 1000000,
FallbackGizmo = 2000000
};
struct HitResult
@ -66,6 +68,7 @@ private:
std::vector<std::shared_ptr<SceneRaycasterItem>> m_bed;
std::vector<std::shared_ptr<SceneRaycasterItem>> m_volumes;
std::vector<std::shared_ptr<SceneRaycasterItem>> m_gizmos;
std::vector<std::shared_ptr<SceneRaycasterItem>> m_fallback_gizmos;
// When set to true, if checking gizmos returns a valid hit,
// the search is not performed on other types
@ -99,9 +102,11 @@ public:
size_t beds_count() const { return m_bed.size(); }
size_t volumes_count() const { return m_volumes.size(); }
size_t gizmos_count() const { return m_gizmos.size(); }
size_t fallback_gizmos_count() const { return m_fallback_gizmos.size(); }
size_t active_beds_count() const;
size_t active_volumes_count() const;
size_t active_gizmos_count() const;
size_t active_fallback_gizmos_count() const;
#endif // ENABLE_RAYCAST_PICKING_DEBUG
static int decode_id(EType type, int id);