diff --git a/resources/icons/cut_connectors.svg b/resources/icons/cut_connectors.svg new file mode 100644 index 000000000..8cd03aa06 --- /dev/null +++ b/resources/icons/cut_connectors.svg @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d4c81299f..79c54748a 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1191,11 +1191,21 @@ size_t ModelObject::parts_count() const return num; } +bool ModelObject::has_connectors() const +{ + assert(is_cut()); + for (const ModelVolume* v : this->volumes) + if (v->cut_info.is_connector) + return true; + + return false; +} + indexed_triangle_set ModelObject::get_connector_mesh(CutConnectorAttributes connector_attributes) { indexed_triangle_set connector_mesh; - int sectorCount; + int sectorCount {1}; switch (CutConnectorShape(connector_attributes.shape)) { case CutConnectorShape::Triangle: sectorCount = 3; @@ -1238,7 +1248,7 @@ void ModelObject::apply_cut_connectors(const std::string& new_name) new_volume->set_transformation(assemble_transform(connector.pos) * connector.rotation_m * scale_transform(Vec3f(connector.radius, connector.radius, connector.height).cast())); - new_volume->cut_info = { true, connector.attribs.type, connector.radius_tolerance, connector.height_tolerance }; + 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()); @@ -1298,7 +1308,8 @@ void ModelVolume::reset_extra_facets() void ModelVolume::apply_tolerance() { - if (!cut_info.is_connector) + assert(cut_info.is_connector); + if (cut_info.is_processed) return; Vec3d sf = get_scaling_factor(); @@ -1321,7 +1332,8 @@ void ModelVolume::apply_tolerance() void ModelObject::process_connector_cut(ModelVolume* volume, ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower, std::vector& dowels, Vec3d& local_dowels_displace) { - volume->cut_info.discard(); + assert(volume->cut_info.is_connector); + volume->cut_info.set_processed(); const auto volume_matrix = volume->get_matrix(); @@ -1546,10 +1558,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, const Transform3d& cut_matrix, volume->reset_extra_facets(); if (!volume->is_model_part()) { - if (volume->cut_info.is_connector) - process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace); - else + if (volume->cut_info.is_processed) process_modifier_cut(volume, instance_matrix, inverse_cut_matrix, attributes, upper, lower); + else + process_connector_cut(volume, attributes, upper, lower, dowels, local_dowels_displace); } else if (!volume->mesh().empty()) process_solid_part_cut(volume, instance_matrix, cut_matrix, attributes, upper, lower, local_displace); diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 16142ec05..cc2c612fe 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -477,6 +477,7 @@ public: 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; @@ -723,14 +724,26 @@ public: struct CutInfo { bool is_connector{ false }; + bool is_processed{ true }; CutConnectorType connector_type{ CutConnectorType::Plug }; - float radius_tolerance;// [0.f : 1.f] - float height_tolerance;// [0.f : 1.f] + float radius_tolerance{ 0.f };// [0.f : 1.f] + float height_tolerance{ 0.f };// [0.f : 1.f] - void discard() { is_connector = false; } + CutInfo() = default; + CutInfo(CutConnectorType type, float rad_tolerance, float h_tolerance) : + is_connector(true), + is_processed(false), + connector_type(type), + radius_tolerance(rad_tolerance), + height_tolerance(h_tolerance) + {} + + void set_processed() { is_processed = true; } }; CutInfo cut_info; + bool is_cut_connector() const { return cut_info.is_processed && cut_info.is_connector; } + // The triangular model. const TriangleMesh& mesh() const { return *m_mesh.get(); } #if ENABLE_RAYCAST_PICKING diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6227c5130..05eb02778 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1881,13 +1881,8 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type) mv->seam_facets.reset(); break; - case InfoItemType::Cut: - if (0) { // #ysFIXME_Cut - cnv->get_gizmos_manager().reset_all_states(); - Plater::TakeSnapshot(plater, _L("Remove cut connectors")); - (*m_objects)[obj_idx]->cut_connectors.clear(); - } else - Slic3r::GUI::show_error(nullptr, _L("Connectors cannot be deleted from cut object.")); + case InfoItemType::CutConnectors: + show_error(nullptr, _L("Connectors cannot be deleted from cut object.")); break; case InfoItemType::MmuSegmentation: @@ -2422,9 +2417,12 @@ bool ObjectList::is_splittable(bool to_objects) auto obj_idx = get_selected_obj_idx(); if (obj_idx < 0) return false; - if ((*m_objects)[obj_idx]->volumes.size() > 1) + const ModelObject* object = (*m_objects)[obj_idx]; + if (object->is_cut()) + return false; + if (object->volumes.size() > 1) return true; - return (*m_objects)[obj_idx]->volumes[0]->is_splittable(); + return object->volumes[0]->is_splittable(); } return false; } @@ -2595,19 +2593,13 @@ void ObjectList::part_selection_changed() } case InfoItemType::CustomSupports: case InfoItemType::CustomSeam: -// case InfoItemType::Cut: case InfoItemType::MmuSegmentation: { GLGizmosManager::EType gizmo_type = info_type == InfoItemType::CustomSupports ? GLGizmosManager::EType::FdmSupports : info_type == InfoItemType::CustomSeam ? GLGizmosManager::EType::Seam : - info_type == InfoItemType::Cut ? GLGizmosManager::EType::Cut : GLGizmosManager::EType::MmuSegmentation; if (gizmos_mgr.get_current_type() != gizmo_type) gizmos_mgr.open_gizmo(gizmo_type); - if (info_type == InfoItemType::Cut) { - GLGizmoCut3D* cut = dynamic_cast(gizmos_mgr.get_current()); - cut->set_connectors_editing(); - } break; } case InfoItemType::Sinking: { break; } @@ -2762,7 +2754,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio for (InfoItemType type : {InfoItemType::CustomSupports, InfoItemType::CustomSeam, - InfoItemType::Cut, + InfoItemType::CutConnectors, InfoItemType::MmuSegmentation, InfoItemType::Sinking, InfoItemType::VariableLayerHeight}) { @@ -2783,11 +2775,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio }); break; - case InfoItemType::Cut : - if (0) // #ysFIXME_Cut - should_show = !model_object->cut_connectors.empty(); - else - should_show = model_object->is_cut() && model_object->volumes.size() > 1; + case InfoItemType::CutConnectors: + should_show = model_object->is_cut() && model_object->has_connectors() && model_object->volumes.size() > 1; break; case InfoItemType::VariableLayerHeight : should_show = printer_technology() == ptFFF @@ -2841,9 +2830,20 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed) update_info_items(obj_idx, nullptr, call_selection_changed); + bool can_add_volumes = model_object->volumes.size() > 1; + if (can_add_volumes && model_object->is_cut()) { + int no_connectors_cnt = 0; + for (const ModelVolume* v : model_object->volumes) + if (!v->is_cut_connector()) + no_connectors_cnt++; + can_add_volumes = no_connectors_cnt > 1; + } + // add volumes to the object - if (model_object->volumes.size() > 1 && !model_object->is_cut()) { + if (can_add_volumes) { for (const ModelVolume* volume : model_object->volumes) { + if (model_object->is_cut() && volume->is_cut_connector()) + continue; const wxDataViewItem& vol_item = m_objects_model->AddVolumeChild(item, from_u8(volume->name), volume->type(), diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 1260ee3bb..97714ea62 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -18,9 +18,17 @@ namespace Slic3r { namespace GUI { -static const double Margin = 20.0; static const ColorRGBA GRABBER_COLOR = ColorRGBA::YELLOW(); +// connector colors +static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW(); +static const ColorRGBA DOWEL_COLOR = ColorRGBA::DARK_YELLOW(); +static const ColorRGBA HOVERED_PLAG_COLOR = ColorRGBA::CYAN(); +static const ColorRGBA HOVERED_DOWEL_COLOR = ColorRGBA(0.0f, 0.5f, 0.5f, 1.0f); +static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); +static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::DARK_GRAY(); +static const ColorRGBA CONNECTOR_DEF_COLOR = ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f); + const unsigned int AngleResolution = 64; const unsigned int ScaleStepsCount = 72; const float ScaleStepRad = 2.0f * float(PI) / ScaleStepsCount; @@ -1426,8 +1434,6 @@ void GLGizmoCut3D::render_connectors_input_window(CutConnectors &connectors) unselect_all_connectors(); set_connectors_editing(false); } - - m_parent.request_extra_frame(); } void GLGizmoCut3D::render_build_size() @@ -1455,10 +1461,22 @@ void GLGizmoCut3D::reset_cut_plane() update_clipper(); } +void GLGizmoCut3D::invalidate_cut_plane() +{ + m_rotation_m = Transform3d::Identity(); + m_plane_center = Vec3d::Zero(); + m_min_pos = Vec3d::Zero(); + m_max_pos = Vec3d::Zero(); + m_bb_center = Vec3d::Zero(); + m_center_offset = Vec3d::Zero(); +} + void GLGizmoCut3D::set_connectors_editing(bool connectors_editing) { m_connectors_editing = connectors_editing; update_raycasters_for_picking(); + + m_parent.request_extra_frame(); } void GLGizmoCut3D::render_cut_plane_input_window(CutConnectors &connectors) @@ -1661,13 +1679,14 @@ void GLGizmoCut3D::render_connectors() pos[Z] += sla_shift; // First decide about the color of the point. - if (size_t(m_hover_id- m_connectors_group_id) == i) - render_color = ColorRGBA::CYAN(); + if (!m_connectors_editing) + render_color = CONNECTOR_DEF_COLOR; + else if (size_t(m_hover_id - m_connectors_group_id) == i) + render_color = connector.attribs.type == CutConnectorType::Dowel ? HOVERED_DOWEL_COLOR : HOVERED_PLAG_COLOR; else if (m_selected[i]) - render_color = ColorRGBA::DARK_GRAY(); + render_color = connector.attribs.type == CutConnectorType::Dowel ? SELECTED_DOWEL_COLOR : SELECTED_PLAG_COLOR; else // neither hover nor picking - render_color = m_connectors_editing ? ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f) : ColorRGBA(1.0f, 0.3f, 0.3f, 0.5f); - + render_color = connector.attribs.type == CutConnectorType::Dowel ? DOWEL_COLOR : PLAG_COLOR; // ! #ysFIXME rework get_volume_transformation if (0) { // else { // neither hover nor picking int mesh_id = -1; @@ -1773,6 +1792,8 @@ void GLGizmoCut3D::perform_cut(const Selection& selection) else { // the object is SLA-elevated and the plane is under it. } + + invalidate_cut_plane(); } @@ -2005,7 +2026,8 @@ bool GLGizmoCut3D::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_posi if (action == SLAGizmoEventType::LeftDown && !shift_down) { // If there is no selection and no hovering, add new point if (m_hover_id == -1 && !control_down && !alt_down) - return add_connector(connectors, mouse_position); + if (!add_connector(connectors, mouse_position)) + unselect_all_connectors(); return true; } if (!m_connectors_editing) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 2df337b04..fe8422865 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -149,6 +149,7 @@ public: void update_clipper(); void update_clipper_on_render(); void set_connectors_editing() { m_connectors_editing = true; } + void invalidate_cut_plane(); BoundingBoxf3 bounding_box() const; BoundingBoxf3 transformed_bounding_box(bool revert_move = false) const; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 16433bc79..594e0716c 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -1228,7 +1228,7 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty case InfoItemType::MmuSegmentation: text += format(_L_PLURAL("%1$d object was loaded with multimaterial painting.", "%1$d objects were loaded with multimaterial painting.",(*it).second), (*it).second) + "\n"; break; case InfoItemType::VariableLayerHeight: text += format(_L_PLURAL("%1$d object was loaded with variable layer height.", "%1$d objects were loaded with variable layer height.", (*it).second), (*it).second) + "\n"; break; case InfoItemType::Sinking: text += format(_L_PLURAL("%1$d object was loaded with partial sinking.", "%1$d objects were loaded with partial sinking.", (*it).second), (*it).second) + "\n"; break; - case InfoItemType::Cut: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break; + case InfoItemType::CutConnectors: text += format(_L_PLURAL("%1$d object was loaded as a part of cut object.", "%1$d objects were loaded as parts of cut object", (*it).second), (*it).second) + "\n"; break; default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break; } } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 61d5bf6ec..4c592b878 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -38,7 +38,7 @@ static constexpr char LayerRootIcon[] = "edit_layers_all"; static constexpr char LayerIcon[] = "edit_layers_some"; static constexpr char WarningIcon[] = "exclamation"; static constexpr char WarningManifoldIcon[] = "exclamation_manifold"; -static constexpr char LockIcon[] = "lock_closed"; +static constexpr char LockIcon[] = "cut_"; struct InfoItemAtributes { std::string name; @@ -49,7 +49,7 @@ const std::map INFO_ITEMS{ // info_item Type info_item Name info_item BitmapName { InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, }, { InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, }, - { InfoItemType::Cut, {L("Cut connectors"), "cut_" }, }, + { InfoItemType::CutConnectors, {L("Cut connectors"), "cut_connectors" }, }, { InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, }, { InfoItemType::Sinking, {L("Sinking"), "sinking"}, }, { InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, }, diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 87be7a4e2..bc9144803 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -51,7 +51,7 @@ enum class InfoItemType Undef, CustomSupports, CustomSeam, - Cut, + CutConnectors, MmuSegmentation, Sinking, VariableLayerHeight diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d09de9cf3..bb3f6e41a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -97,6 +97,7 @@ #include "MsgDialog.hpp" #include "ProjectDirtyStateManager.hpp" #include "Gizmos/GLGizmoSimplify.hpp" // create suggestion notification +#include "Gizmos/GLGizmoCut.hpp" #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -2980,6 +2981,9 @@ void Plater::priv::object_list_changed() const bool model_fits = view3D->get_canvas3d()->check_volumes_outside_state() == ModelInstancePVS_Inside; sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits); + + // invalidate CutGizmo after changes in ObjectList + static_cast(q->canvas3D()->get_gizmos_manager().get_gizmo(GLGizmosManager::Cut))->invalidate_cut_plane(); } void Plater::priv::select_all()