diff --git a/resources/models/sl1_bed.stl b/resources/models/sl1_bed.stl new file mode 100644 index 000000000..28601b288 Binary files /dev/null and b/resources/models/sl1_bed.stl differ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index acaba9d6a..1ef6c7b84 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1125,7 +1125,7 @@ std::string Print::validate() const // Check horizontal clearance. { Polygons convex_hulls_other; - for (PrintObject *object : m_objects) { + for (const PrintObject *object : m_objects) { // Get convex hull of all meshes assigned to this print object. Polygon convex_hull; { @@ -1186,46 +1186,63 @@ std::string Print::validate() const return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); - SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters(); - const PrintObject* tallest_object = m_objects.front(); // let's find the tallest object - for (const auto* object : m_objects) - if (*(object->layer_height_profile.end()-2) > *(tallest_object->layer_height_profile.end()-2) ) - tallest_object = object; - - for (PrintObject *object : m_objects) { - SlicingParameters slicing_params = object->slicing_parameters(); - if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON || - std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON) - return L("The Wipe Tower is only supported for multiple objects if they have equal layer heigths"); - if (slicing_params.raft_layers() != slicing_params0.raft_layers()) - return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers"); - if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance) - return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"); - if (! equal_layering(slicing_params, slicing_params0)) - return L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); - - if (m_config.variable_layer_height) { // comparing layer height profiles - bool failed = false; - // layer_height_profile should be set by Print::apply(). - if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size()) { - int i = 0; - while (i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) { - if (std::abs(tallest_object->layer_height_profile[i] - object->layer_height_profile[i])) { - failed = true; - break; - } - ++i; - if (i == object->layer_height_profile.size()-2) // this element contains this objects max z - if (tallest_object->layer_height_profile[i] > object->layer_height_profile[i]) // the difference does not matter in this case - ++i; - } + if (m_objects.size() > 1) { + bool has_custom_layering = false; + std::vector> layer_height_profiles; + for (const PrintObject *object : m_objects) { + has_custom_layering = ! object->model_object()->layer_height_ranges.empty() || ! object->model_object()->layer_height_profile.empty(); + if (has_custom_layering) { + layer_height_profiles.assign(m_objects.size(), std::vector()); + break; } - else - failed = true; + } + SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters(); + size_t tallest_object_idx = 0; + if (has_custom_layering) + PrintObject::update_layer_height_profile(*m_objects.front()->model_object(), slicing_params0, layer_height_profiles.front()); + for (size_t i = 1; i < m_objects.size(); ++ i) { + const PrintObject *object = m_objects[i]; + const SlicingParameters slicing_params = object->slicing_parameters(); + if (std::abs(slicing_params.first_print_layer_height - slicing_params0.first_print_layer_height) > EPSILON || + std::abs(slicing_params.layer_height - slicing_params0.layer_height ) > EPSILON) + return L("The Wipe Tower is only supported for multiple objects if they have equal layer heigths"); + if (slicing_params.raft_layers() != slicing_params0.raft_layers()) + return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers"); + if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance) + return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"); + if (! equal_layering(slicing_params, slicing_params0)) + return L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); + if (has_custom_layering) { + PrintObject::update_layer_height_profile(*object->model_object(), slicing_params, layer_height_profiles[i]); + if (*(layer_height_profiles[i].end()-2) > *(layer_height_profiles[tallest_object_idx].end()-2)) + tallest_object_idx = i; + } + } - if (failed) - return L("The Wipe tower is only supported if all objects have the same layer height profile"); + if (has_custom_layering) { + const std::vector &layer_height_profile_tallest = layer_height_profiles[tallest_object_idx]; + for (size_t idx_object = 0; idx_object < m_objects.size(); ++ idx_object) { + const PrintObject *object = m_objects[idx_object]; + const std::vector &layer_height_profile = layer_height_profiles[idx_object]; + bool failed = false; + if (layer_height_profile_tallest.size() >= layer_height_profile.size()) { + int i = 0; + while (i < layer_height_profile.size() && i < layer_height_profile_tallest.size()) { + if (std::abs(layer_height_profile_tallest[i] - layer_height_profile[i])) { + failed = true; + break; + } + ++ i; + if (i == layer_height_profile.size() - 2) // this element contains this objects max z + if (layer_height_profile_tallest[i] > layer_height_profile[i]) // the difference does not matter in this case + ++ i; + } + } else + failed = true; + if (failed) + return L("The Wipe tower is only supported if all objects have the same layer height profile"); + } } } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index d0591e764..3de91818a 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -83,10 +83,6 @@ public: // vector of (vectors of volume ids), indexed by region_id std::vector> region_volumes; - // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. - // The pairs of are packed into a 1D array. - std::vector layer_height_profile; - // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops bool typed_slices; @@ -175,7 +171,7 @@ private: void infill(); void generate_support_material(); - void _slice(); + void _slice(const std::vector &layer_height_profile); std::string _fix_slicing_errors(); void _simplify_slices(double distance); void _make_perimeters(); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 9dd101123..d516153a9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -64,8 +64,6 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta } this->set_copies(copies); } - - this->layer_height_profile = model_object->layer_height_profile; } PrintBase::ApplyStatus PrintObject::set_copies(const Points &points) @@ -105,9 +103,10 @@ void PrintObject::slice() if (! this->set_started(posSlice)) return; m_print->set_status(10, "Processing triangulated mesh"); - this->update_layer_height_profile(*this->model_object(), this->slicing_parameters(), this->layer_height_profile); + std::vector layer_height_profile; + this->update_layer_height_profile(*this->model_object(), this->slicing_parameters(), layer_height_profile); m_print->throw_if_canceled(); - this->_slice(); + this->_slice(layer_height_profile); m_print->throw_if_canceled(); // Fix the model. //FIXME is this the right place to do? It is done repeateadly at the UI and now here at the backend. @@ -1438,7 +1437,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // Resulting expolygons of layer regions are marked as Internal. // // this should be idempotent -void PrintObject::_slice() +void PrintObject::_slice(const std::vector &layer_height_profile) { BOOST_LOG_TRIVIAL(info) << "Slicing objects..." << log_memory_info(); @@ -1457,7 +1456,7 @@ void PrintObject::_slice() { this->clear_layers(); // Object layers (pairs of bottom/top Z coordinate), without the raft. - std::vector object_layers = generate_object_layers(slicing_params, this->layer_height_profile); + std::vector object_layers = generate_object_layers(slicing_params, layer_height_profile); // Reserve object layers for the raft. Last layer of the raft is the contact layer. int id = int(slicing_params.raft_layers()); slice_zs.reserve(object_layers.size()); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 6477010fd..1d7858ead 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -738,45 +738,46 @@ public: const TriangleMesh& merged_mesh() const { if(meshcache_valid) return meshcache; - meshcache = TriangleMesh(); + Contour3D merged; for(auto& head : heads()) { if(m_ctl.stopcondition()) break; - if(head.is_valid()) { - auto&& m = mesh(head.mesh); - meshcache.merge(m); - } + if(head.is_valid()) + merged.merge(head.mesh); } for(auto& stick : pillars()) { if(m_ctl.stopcondition()) break; - meshcache.merge(mesh(stick.mesh)); - meshcache.merge(mesh(stick.base)); + merged.merge(stick.mesh); + merged.merge(stick.base); } for(auto& j : junctions()) { if(m_ctl.stopcondition()) break; - meshcache.merge(mesh(j.mesh)); + merged.merge(j.mesh); } for(auto& cb : compact_bridges()) { if(m_ctl.stopcondition()) break; - meshcache.merge(mesh(cb.mesh)); + merged.merge(cb.mesh); } for(auto& bs : bridges()) { if(m_ctl.stopcondition()) break; - meshcache.merge(mesh(bs.mesh)); + merged.merge(bs.mesh); } + if(m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); return meshcache; } + meshcache = mesh(merged); + // TODO: Is this necessary? - meshcache.repair(); + //meshcache.repair(); BoundingBoxf3&& bb = meshcache.bounding_box(); model_height = bb.max(Z) - bb.min(Z); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 05bb07616..b02def988 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -45,7 +45,7 @@ // Improves navigation between sidebar fields #define ENABLE_IMPROVED_SIDEBAR_OBJECTS_MANIPULATION (1 && ENABLE_1_42_0_ALPHA2) // Adds print bed models to 3D scene -#define ENABLE_PRINT_BED_MODELS (0 && ENABLE_1_42_0_ALPHA2) +#define ENABLE_PRINT_BED_MODELS (1 && ENABLE_1_42_0_ALPHA2) #endif // _technologies_h_ @@ -62,3 +62,5 @@ #define ENABLE_GENERIC_SUBPARTS_PLACEMENT (1 && ENABLE_1_42_0_ALPHA4) // Reworked management of bed shape changes #define ENABLE_REWORKED_BED_SHAPE_CHANGE (1 && ENABLE_1_42_0_ALPHA4) +// Use anisotropic filtering on bed plate texture +#define ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES (1 && ENABLE_1_42_0_ALPHA4) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 8a0a087c0..ff8c20289 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -99,6 +99,8 @@ TriangleMesh& TriangleMesh::operator=(const TriangleMesh &other) return *this; } +// #define SLIC3R_TRACE_REPAIR + void TriangleMesh::repair() { if (this->repaired) return; @@ -109,7 +111,9 @@ void TriangleMesh::repair() BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; // checking exact +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_check_facets_exact(&stl); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); @@ -124,7 +128,9 @@ void TriangleMesh::repair() for (int i = 0; i < iterations; i++) { if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); //last_edges_fixed = stl.stats.edges_fixed; @@ -137,7 +143,9 @@ void TriangleMesh::repair() // remove_unconnected if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_remove_unconnected_facets(&stl); } @@ -146,26 +154,36 @@ void TriangleMesh::repair() // Don't fill holes, the current algorithm does more harm than good on complex holes. // Rather let the slicing algorithm close gaps in 2D slices. if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fill_holes"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_fill_holes(&stl); stl_clear_error(&stl); } #endif // normal_directions +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_directions"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_directions(&stl); // normal_values +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_fix_normal_values"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_fix_normal_values(&stl); // always calculate the volume and reverse all normals if volume is negative +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_calculate_volume"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_calculate_volume(&stl); // neighbors +#ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_verify_neighbors"; +#endif /* SLIC3R_TRACE_REPAIR */ stl_verify_neighbors(&stl); this->repaired = true; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 0bd3d26ca..1a6c3b5b0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -545,6 +545,11 @@ void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta) const std::string model_path = resources_dir() + "/models/" + key; #endif // ENABLE_PRINT_BED_MODELS +#if ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES + GLfloat max_anisotropy = 0.0f; + ::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy); +#endif // ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES + std::string filename = tex_path + "_top.png"; if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename)) { @@ -553,6 +558,14 @@ void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta) const _render_custom(); return; } +#if ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES + if (max_anisotropy > 0.0f) + { + ::glBindTexture(GL_TEXTURE_2D, m_top_texture.get_id()); + ::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy); + ::glBindTexture(GL_TEXTURE_2D, 0); + } +#endif // ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES } filename = tex_path + "_bottom.png"; @@ -563,6 +576,14 @@ void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta) const _render_custom(); return; } +#if ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES + if (max_anisotropy > 0.0f) + { + ::glBindTexture(GL_TEXTURE_2D, m_bottom_texture.get_id()); + ::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy); + ::glBindTexture(GL_TEXTURE_2D, 0); + } +#endif // ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES } #if ENABLE_PRINT_BED_MODELS @@ -570,7 +591,7 @@ void GLCanvas3D::Bed::_render_prusa(const std::string &key, float theta) const { filename = model_path + "_bed.stl"; if ((m_model.get_filename() != filename) && m_model.init_from_file(filename, useVBOs)) - m_model.center_around(m_bounding_box.center() - Vec3d(0.0, 0.0, 1.0 + 0.5 * m_model.get_bounding_box().size()(2))); + m_model.center_around(m_bounding_box.center() - Vec3d(0.0, 0.0, 0.1 + 0.5 * m_model.get_bounding_box().size()(2))); if (!m_model.get_filename().empty()) { @@ -1179,11 +1200,12 @@ void GLCanvas3D::LayersEditing::adjust_layer_height_profile() m_layers_texture.valid = false; } -void GLCanvas3D::LayersEditing::reset_layer_height_profile() +void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) { const_cast(m_model_object)->layer_height_profile.clear(); m_layer_height_profile.clear(); m_layers_texture.valid = false; + canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } void GLCanvas3D::LayersEditing::generate_layer_height_texture() @@ -5117,7 +5139,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (evt.LeftDown()) { // A volume is selected and the mouse is inside the reset button. Reset the ModelObject's layer height profile. - m_layers_editing.reset_layer_height_profile(); + m_layers_editing.reset_layer_height_profile(*this); // Index 2 means no editing, just wait for mouse up event. m_layers_editing.state = LayersEditing::Completed; @@ -5251,7 +5273,8 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_volumes.volumes[m_hover_volume_id]->hover && !m_volumes.volumes[m_hover_volume_id]->is_wipe_tower) { // forces the selection of the volume - m_selection.add(m_hover_volume_id); + if (!m_selection.is_multiple_full_instance()) + m_selection.add(m_hover_volume_id); m_gizmos.update_on_off_state(m_selection); post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT)); _update_gizmos_data(); @@ -5822,6 +5845,7 @@ void GLCanvas3D::set_camera_zoom(float zoom) void GLCanvas3D::update_gizmos_on_off_state() { set_as_dirty(); + _update_gizmos_data(); m_gizmos.update_on_off_state(get_selection()); } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 38f02ff8f..ab53d5048 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -360,7 +360,7 @@ class GLCanvas3D void adjust_layer_height_profile(); void accept_changes(GLCanvas3D& canvas); - void reset_layer_height_profile(); + void reset_layer_height_profile(GLCanvas3D& canvas); static float get_cursor_z_relative(const GLCanvas3D& canvas); static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 1f37b7aec..2eba6f741 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -1511,7 +1511,7 @@ void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) bool object_changed = m_model_object != model_object; m_model_object = model_object; - if (object_changed && is_plane_update_necessary()) + if (model_object && (object_changed || is_plane_update_necessary())) update_planes(); } @@ -1585,15 +1585,17 @@ void GLGizmoFlatten::update_planes() m_planes.pop_back(); } + // Let's prepare transformation of the normal vector from mesh to instance coordinates. + Geometry::Transformation t(inst_matrix); + Vec3d scaling = t.get_scaling_factor(); + t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + // Now we'll go through all the polygons, transform the points into xy plane to process them: for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { Pointf3s& polygon = m_planes[polygon_id].vertices; const Vec3d& normal = m_planes[polygon_id].normal; - // let's transform the normal accodring to the instance matrix: - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./(scaling(0)*scaling(0)), 1./(scaling(0)*scaling(0)), 1./(scaling(0)*scaling(0)))); + // transform the normal according to the instance matrix: Vec3d normal_transformed = t.get_matrix() * normal; // We are going to rotate about z and y to flatten the plane diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9991fee75..462f3c328 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -194,6 +194,8 @@ bool GUI_App::OnInit() preset_updater->slic3r_update_notify(); } preset_updater->sync(preset_bundle); + + load_current_presets(); }); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3426fd02a..a880b2cbe 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -51,6 +51,7 @@ ObjectList::ObjectList(wxWindow* parent) : create_object_popupmenu(&m_menu_object); create_part_popupmenu(&m_menu_part); create_sla_object_popupmenu(&m_menu_sla_object); + create_instance_popupmenu(&m_menu_instance); // describe control behavior Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxEvent& event) { @@ -97,7 +98,7 @@ void ObjectList::create_objects_ctrl() // temporary workaround for the correct behavior of the Scrolled sidebar panel: // 1. set a height of the list to some big value // 2. change it to the normal min value (200) after first whole App updating/layouting - SetMinSize(wxSize(-1, 1500)); // #ys_FIXME + SetMinSize(wxSize(-1, 3000)); // #ys_FIXME m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(this, 1, wxGROW | wxLEFT, 20); @@ -400,15 +401,28 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) void ObjectList::show_context_menu() { + if (multiple_selection() && selected_instances_of_same_object()) + { + wxGetApp().plater()->PopupMenu(&m_menu_instance); + + wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { + evt.Enable(can_split_instances()); }, m_menu_item_split_instances->GetId()); + return; + } + const auto item = GetSelection(); if (item) { - if (!(m_objects_model->GetItemType(item) & (itObject | itVolume))) + const ItemType type = m_objects_model->GetItemType(item); + if (!(type & (itObject | itVolume | itInstance))) return; - wxMenu* menu = m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : + + wxMenu* menu = type & itInstance ? &m_menu_instance : + m_objects_model->GetParent(item) != wxDataViewItem(0) ? &m_menu_part : wxGetApp().plater()->printer_technology() == ptFFF ? &m_menu_object : &m_menu_sla_object; - append_menu_item_settings(menu); + if (!(type & itInstance)) + append_menu_item_settings(menu); wxGetApp().plater()->PopupMenu(menu); @@ -443,15 +457,35 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) { const wxDataViewItem item(event.GetItem()); - // only allow drags for item, not containers - if (multiple_selection() || GetSelection()!=item || - m_objects_model->GetParent(item) == wxDataViewItem(0) || - m_objects_model->GetItemType(item) != itVolume ) { + const bool mult_sel = multiple_selection(); + + if (mult_sel && !selected_instances_of_same_object() || + !mult_sel && (GetSelection() != item || + m_objects_model->GetParent(item) == wxDataViewItem(0) ) ) { + event.Veto(); + return; + } + + const ItemType& type = m_objects_model->GetItemType(item); + if (!(type & (itVolume | itInstance))) { event.Veto(); return; } - m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), m_objects_model->GetVolumeIdByItem(item)); + if (mult_sel) + { + m_dragged_data.init(m_objects_model->GetObjectIdByItem(item),type); + std::set& sub_obj_idxs = m_dragged_data.inst_idxs(); + wxDataViewItemArray sels; + GetSelections(sels); + for (auto sel : sels ) + sub_obj_idxs.insert(m_objects_model->GetInstanceIdByItem(sel)); + } + else + m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), + type&itVolume ? m_objects_model->GetVolumeIdByItem(item) : + m_objects_model->GetInstanceIdByItem(item), + type); /* Under MSW or OSX, DnD moves an item to the place of another selected item * But under GTK, DnD moves an item between another two items. @@ -470,31 +504,41 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) event.SetDragFlags(wxDrag_DefaultMove); // allows both copy and move; } +bool ObjectList::can_drop(const wxDataViewItem& item) const +{ + return m_dragged_data.type() == itInstance && !item.IsOk() || + m_dragged_data.type() == itVolume && item.IsOk() && + m_objects_model->GetItemType(item) == itVolume && + m_dragged_data.obj_idx() == m_objects_model->GetObjectIdByItem(item); +} + void ObjectList::OnDropPossible(wxDataViewEvent &event) { - wxDataViewItem item(event.GetItem()); + const wxDataViewItem& item = event.GetItem(); - // only allow drags for item or background, not containers - if (!item.IsOk() || - m_objects_model->GetParent(item) == wxDataViewItem(0) || - m_objects_model->GetItemType(item) != itVolume || - m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) + if (!can_drop(item)) event.Veto(); } void ObjectList::OnDrop(wxDataViewEvent &event) { - wxDataViewItem item(event.GetItem()); + const wxDataViewItem& item = event.GetItem(); - if (!item.IsOk() || m_objects_model->GetParent(item) == wxDataViewItem(0) || - m_objects_model->GetItemType(item) != itVolume || - m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) { + if (!can_drop(item)) + { event.Veto(); m_dragged_data.clear(); return; } - const int from_volume_id = m_dragged_data.vol_idx(); + if (m_dragged_data.type() == itInstance) + { + instances_to_separated_object(m_dragged_data.obj_idx(), m_dragged_data.inst_idxs()); + m_dragged_data.clear(); + return; + } + + const int from_volume_id = m_dragged_data.sub_obj_idx(); int to_volume_id = m_objects_model->GetVolumeIdByItem(item); // It looks like a fixed in current version of the wxWidgets @@ -506,7 +550,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) // if (to_volume_id > from_volume_id) to_volume_id--; // #endif // __WXGTK__ - auto& volumes = (*m_objects)[/*m_selected_object_id*/m_dragged_data.obj_idx()]->volumes; + auto& volumes = (*m_objects)[m_dragged_data.obj_idx()]->volumes; auto delta = to_volume_id < from_volume_id ? -1 : 1; int cnt = 0; for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) @@ -516,7 +560,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) m_objects_model->GetParent(item))); m_parts_changed = true; - parts_changed(/*m_selected_object_id*/m_dragged_data.obj_idx()); + parts_changed(m_dragged_data.obj_idx()); m_dragged_data.clear(); } @@ -741,6 +785,12 @@ wxMenuItem* ObjectList::append_menu_item_change_type(wxMenu* menu) } +wxMenuItem* ObjectList::append_menu_item_instance_to_object(wxMenu* menu) +{ + return append_menu_item(menu, wxID_ANY, _(L("Set as a Separated Object")), "", + [this](wxCommandEvent&) { split_instances(); }, "", menu); +} + void ObjectList::create_object_popupmenu(wxMenu *menu) { append_menu_items_add_volume(menu); @@ -769,6 +819,11 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) menu->AppendSeparator(); } +void ObjectList::create_instance_popupmenu(wxMenu*menu) +{ + m_menu_item_split_instances = append_menu_item_instance_to_object(menu); +} + wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu) { wxMenu *menu = new wxMenu; @@ -1116,6 +1171,27 @@ bool ObjectList::is_splittable() return splittable; } +bool ObjectList::selected_instances_of_same_object() +{ + wxDataViewItemArray sels; + GetSelections(sels); + + const int obj_idx = m_objects_model->GetObjectIdByItem(sels.front()); + + for (auto item : sels) { + if (! (m_objects_model->GetItemType(item) & itInstance) || + obj_idx != m_objects_model->GetObjectIdByItem(item)) + return false; + } + return true; +} + +bool ObjectList::can_split_instances() +{ + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + return selection.is_multiple_full_instance() || selection.is_single_full_instance(); +} + void ObjectList::part_settings_changed() { m_part_settings_changed = true; @@ -1419,7 +1495,7 @@ bool ObjectList::multiple_selection() const void ObjectList::update_selections() { - auto& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); wxDataViewItemArray sels; // We doesn't update selection if SettingsItem for the current object/part is selected @@ -1505,7 +1581,7 @@ void ObjectList::update_selections() void ObjectList::update_selections_on_canvas() { - auto& selection = wxGetApp().plater()->canvas3D()->get_selection(); + GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); const int sel_cnt = GetSelectedItemsCount(); if (sel_cnt == 0) { @@ -1729,6 +1805,43 @@ void ObjectList::update_settings_items() UnselectAll(); } +void ObjectList::instances_to_separated_object(const int obj_idx, const std::set& inst_idxs) +{ + // create new object from selected instance + ModelObject* model_object = (*m_objects)[obj_idx]->get_model()->add_object(*(*m_objects)[obj_idx]); + for (int inst_idx = model_object->instances.size() - 1; inst_idx >= 0; inst_idx--) + { + if (find(inst_idxs.begin(), inst_idxs.end(), inst_idx) != inst_idxs.end()) + continue; + model_object->delete_instance(inst_idx); + } + + // Add new object to the object_list + add_object_to_list(m_objects->size() - 1); + + for (std::set::const_reverse_iterator it = inst_idxs.rbegin(); it != inst_idxs.rend(); ++it) + { + // delete selected instance from the object + del_subobject_from_object(obj_idx, *it, itInstance); + delete_instance_from_list(obj_idx, *it); + } +} + +void ObjectList::split_instances() +{ + const GLCanvas3D::Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + const int obj_idx = selection.get_object_idx(); + if (obj_idx == -1) + return; + + const int inst_idx = selection.get_instance_idx(); + const std::set inst_idxs = inst_idx < 0 ? + selection.get_instance_idxs() : + std::set{ inst_idx }; + + instances_to_separated_object(obj_idx, inst_idxs); +} + void ObjectList::ItemValueChanged(wxDataViewEvent &event) { if (event.GetColumn() == 0) diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 7631782df..f24eb95b0 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -56,22 +56,38 @@ class ObjectList : public wxDataViewCtrl struct dragged_item_data { - void init(const int obj_idx, const int vol_idx) { + void init(const int obj_idx, const int subobj_idx, const ItemType type) { m_obj_idx = obj_idx; - m_vol_idx = vol_idx; + m_type = type; + if (m_type&itVolume) + m_vol_idx = subobj_idx; + else + m_inst_idxs.insert(subobj_idx); + } + + void init(const int obj_idx, const ItemType type) { + m_obj_idx = obj_idx; + m_type = type; } void clear() { m_obj_idx = -1; - m_vol_idx = -1; + m_vol_idx = -1; + m_inst_idxs.clear(); + m_type = itUndef; } int obj_idx() const { return m_obj_idx; } - int vol_idx() const { return m_vol_idx; } + int sub_obj_idx() const { return m_vol_idx; } + ItemType type() const { return m_type; } + std::set& inst_idxs() { return m_inst_idxs; } private: int m_obj_idx = -1; int m_vol_idx = -1; + std::set m_inst_idxs{}; + ItemType m_type = itUndef; + } m_dragged_data; wxBoxSizer *m_sizer {nullptr}; @@ -91,9 +107,11 @@ class ObjectList : public wxDataViewCtrl wxMenu m_menu_object; wxMenu m_menu_part; wxMenu m_menu_sla_object; + wxMenu m_menu_instance; wxMenuItem* m_menu_item_split { nullptr }; wxMenuItem* m_menu_item_split_part { nullptr }; wxMenuItem* m_menu_item_settings { nullptr }; + wxMenuItem* m_menu_item_split_instances { nullptr }; std::vector m_bmp_vector; @@ -148,9 +166,11 @@ public: wxMenuItem* append_menu_item_split(wxMenu* menu); wxMenuItem* append_menu_item_settings(wxMenu* menu); wxMenuItem* append_menu_item_change_type(wxMenu* menu); + wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu); void create_object_popupmenu(wxMenu *menu); void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); + void create_instance_popupmenu(wxMenu*menu); wxMenu* create_settings_popupmenu(wxMenu *parent_menu); void update_opt_keys(t_config_option_keys& t_optopt_keys); @@ -166,6 +186,8 @@ public: void split(); bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume); bool is_splittable(); + bool selected_instances_of_same_object(); + bool can_split_instances(); wxPoint get_mouse_position_in_control(); wxBoxSizer* get_sizer() {return m_sizer;} @@ -222,6 +244,9 @@ public: bool has_multi_part_objects(); void update_settings_items(); + void instances_to_separated_object(const int obj_idx, const std::set& inst_idx); + void split_instances(); + private: void OnChar(wxKeyEvent& event); void OnContextMenu(wxDataViewEvent &event); @@ -229,6 +254,7 @@ private: void OnBeginDrag(wxDataViewEvent &event); void OnDropPossible(wxDataViewEvent &event); void OnDrop(wxDataViewEvent &event); + bool can_drop(const wxDataViewItem& item) const ; void ItemValueChanged(wxDataViewEvent &event); void OnEditingDone(wxDataViewEvent &event); diff --git a/src/slic3r/GUI/I18N.hpp b/src/slic3r/GUI/I18N.hpp index c86ff7501..a899eaa59 100644 --- a/src/slic3r/GUI/I18N.hpp +++ b/src/slic3r/GUI/I18N.hpp @@ -2,6 +2,10 @@ #define _(s) Slic3r::GUI::I18N::translate((s)) #endif /* _ */ +#ifndef _CTX +#define _CTX(s, ctx) Slic3r::GUI::I18N::translate((s), (ctx)) +#endif /* _ */ + #ifndef L // !!! If you needed to translate some wxString, // !!! please use _(L(string)) @@ -21,6 +25,7 @@ #define slic3r_GUI_I18N_hpp_ #include +#include namespace Slic3r { namespace GUI { @@ -29,7 +34,20 @@ namespace I18N { inline wxString translate(const wchar_t *s) { return wxGetTranslation(s); } inline wxString translate(const std::string &s) { return wxGetTranslation(wxString(s.c_str(), wxConvUTF8)); } inline wxString translate(const std::wstring &s) { return wxGetTranslation(s.c_str()); } -} + +#if wxCHECK_VERSION(3, 1, 1) + #define _wxGetTranslation_ctx(S, CTX) wxGetTranslation((S), wxEmptyString, (CTX)) +#else + #define _wxGetTranslation_ctx(S, CTX) ((void)(CTX), wxGetTranslation((S))) +#endif + + inline wxString translate(const char *s, const char* ctx) { return _wxGetTranslation_ctx(wxString(s, wxConvUTF8), ctx); } + inline wxString translate(const wchar_t *s, const char* ctx) { return _wxGetTranslation_ctx(s, ctx); } + inline wxString translate(const std::string &s, const char* ctx) { return _wxGetTranslation_ctx(wxString(s.c_str(), wxConvUTF8), ctx); } + inline wxString translate(const std::wstring &s, const char* ctx) { return _wxGetTranslation_ctx(s.c_str(), ctx); } + +#undef _wxGetTranslation_ctx +} // Return translated std::string as a wxString wxString L_str(const std::string &str); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 9c74861c9..46a46e950 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -155,7 +155,6 @@ void MainFrame::create_preset_tabs() add_created_tab(new TabSLAPrint(m_tabpanel)); add_created_tab(new TabSLAMaterial(m_tabpanel)); add_created_tab(new TabPrinter(m_tabpanel)); - GUI::wxGetApp().load_current_presets(); } void MainFrame::add_created_tab(Tab* panel) diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 4952398f3..78bbe4fec 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -234,7 +234,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // wxString str_label = _(option.label); //! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1 wxString str_label = (option.label == "Top" || option.label == "Bottom") ? - wxGETTEXT_IN_CONTEXT("Layers", wxString(option.label)) : + _CTX(option.label, "Layers") : _(option.label); label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize); label->SetFont(label_font); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ac4988a6c..37b55d89f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1063,6 +1063,7 @@ private: bool can_delete_object() const; bool can_increase_instances() const; bool can_decrease_instances() const; + bool can_set_instance_to_object() const; bool can_split_to_objects() const; bool can_split_to_volumes() const; bool can_split() const; @@ -2365,11 +2366,16 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ [this](wxCommandEvent&) { q->decrease_instances(); }, "delete.png"); wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _(L("Set number of copies")) + dots, _(L("Change the number of copies of the selected object")), [this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png"); + + menu->AppendSeparator(); + wxMenuItem* item_instance_to_object = sidebar->obj_list()->append_menu_item_instance_to_object(menu); + if (q != nullptr) { q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_increase_instances()); }, item_increase->GetId()); q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_decrease_instances()); }, item_decrease->GetId()); q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_increase_instances()); }, item_set_number_of_copies->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_set_instance_to_object()); }, item_instance_to_object->GetId()); } menu->AppendSeparator(); @@ -2432,9 +2438,9 @@ bool Plater::priv::complit_init_object_menu() // ui updates needs to be binded to the parent panel if (q != nullptr) { - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_objects() || can_split_to_volumes*/()); }, item_split->GetId()); - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_objects*/()); }, item_split_objects->GetId()); - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_volumes*/()); }, item_split_volumes->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split_objects->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split_volumes->GetId()); } return true; } @@ -2453,7 +2459,7 @@ bool Plater::priv::complit_init_sla_object_menu() // ui updates needs to be binded to the parent panel if (q != nullptr) { - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_objects*/()); }, item_split->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split->GetId()); } return true; @@ -2472,7 +2478,7 @@ bool Plater::priv::complit_init_part_menu() // ui updates needs to be binded to the parent panel if (q != nullptr) { - q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split/*_to_volumes*/()); }, item_split->GetId()); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_split()); }, item_split->GetId()); } return true; @@ -2540,6 +2546,12 @@ bool Plater::priv::can_increase_instances() const return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()); } +bool Plater::priv::can_set_instance_to_object() const +{ + const int obj_idx = get_selected_object_idx(); + return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); +} + bool Plater::priv::can_decrease_instances() const { int obj_idx = get_selected_object_idx(); diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 52eb3180d..55c0b351f 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -406,7 +406,7 @@ void PrusaObjectDataViewModelNode::set_object_action_icon() { m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("add_object.png")), wxBITMAP_TYPE_PNG); } void PrusaObjectDataViewModelNode::set_part_action_icon() { - m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var("cog.png")), wxBITMAP_TYPE_PNG); + m_action_icon = wxBitmap(Slic3r::GUI::from_u8(Slic3r::var(m_type == itVolume ? "cog.png" : "brick_go.png")), wxBITMAP_TYPE_PNG); } Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index fcce18e10..10709786c 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -275,6 +275,7 @@ public: else if (type == itInstance) { m_idx = parent->GetChildCount(); m_name = wxString::Format("Instance_%d", m_idx+1); + set_part_action_icon(); } } diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index aa90a3ad9..b8662dcd8 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -49,8 +49,6 @@ _constant() Ref config() %code%{ RETVAL = &THIS->config(); %}; Points copies(); - std::vector layer_height_profile() - %code%{ RETVAL = THIS->layer_height_profile; %}; Clone bounding_box(); Points _shifted_copies()