From 564fa9e4dc0b5709428e061cdee1904d1949ada9 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 12 Nov 2018 09:54:04 +0100 Subject: [PATCH 1/6] Enhanced volumes manipulation 2 (scaling in local system) --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- src/slic3r/GUI/GLGizmo.cpp | 51 +++++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9fc01a8c0..9293adf5a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -5165,7 +5165,7 @@ void GLCanvas3D::_update_gizmos_data() bool enable_move_z = !m_selection.is_wipe_tower(); m_gizmos.enable_grabber(Gizmos::Move, 2, enable_move_z); - bool enable_scale_xyz = m_selection.is_single_full_instance(); + bool enable_scale_xyz = m_selection.is_single_full_instance() || m_selection.is_single_volume() || m_selection.is_single_modifier(); for (int i = 0; i < 6; ++i) { m_gizmos.enable_grabber(Gizmos::Scale, i, enable_scale_xyz); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index bf022ba6a..890558321 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -343,7 +343,7 @@ void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const return; const BoundingBoxf3& box = selection.get_bounding_box(); - bool single_instance = selection.is_single_full_instance(); + bool single_selection = selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume(); std::string axis; switch (m_axis) @@ -353,7 +353,7 @@ void GLGizmoRotate::on_render(const GLCanvas3D::Selection& selection) const case Z: { axis = "Z: "; break; } } - if ((single_instance && (m_hover_id == 0)) || m_dragging) + if ((single_selection && (m_hover_id == 0)) || m_dragging) set_tooltip(axis + format((float)Geometry::rad2deg(m_angle), 4) + "\u00B0"); else { @@ -720,19 +720,26 @@ void GLGizmoScale3D::on_process_double_click() void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const { bool single_instance = selection.is_single_full_instance(); + bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); + bool single_selection = single_instance || single_volume; + + Vec3f scale = 100.0f * Vec3f::Ones(); #if ENABLE_MODELVOLUME_TRANSFORM - Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast() : 100.0f * m_scale.cast(); + if (single_instance) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_scaling_factor().cast(); + else if (single_volume) + scale = 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_volume_scaling_factor().cast(); #else Vec3f scale = single_instance ? 100.0f * selection.get_volume(*selection.get_volume_idxs().begin())->get_scaling_factor().cast() : 100.0f * m_scale.cast(); #endif // ENABLE_MODELVOLUME_TRANSFORM - if ((single_instance && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) + if ((single_selection && ((m_hover_id == 0) || (m_hover_id == 1))) || m_grabbers[0].dragging || m_grabbers[1].dragging) set_tooltip("X: " + format(scale(0), 4) + "%"); - else if ((single_instance && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) + else if ((single_selection && ((m_hover_id == 2) || (m_hover_id == 3))) || m_grabbers[2].dragging || m_grabbers[3].dragging) set_tooltip("Y: " + format(scale(1), 4) + "%"); - else if ((single_instance && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) + else if ((single_selection && ((m_hover_id == 4) || (m_hover_id == 5))) || m_grabbers[4].dragging || m_grabbers[5].dragging) set_tooltip("Z: " + format(scale(2), 4) + "%"); - else if ((single_instance && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) + else if ((single_selection && ((m_hover_id == 6) || (m_hover_id == 7) || (m_hover_id == 8) || (m_hover_id == 9))) || m_grabbers[6].dragging || m_grabbers[7].dragging || m_grabbers[8].dragging || m_grabbers[9].dragging) { std::string tooltip = "X: " + format(scale(0), 4) + "%\n"; @@ -761,21 +768,31 @@ void GLGizmoScale3D::on_render(const GLCanvas3D::Selection& selection) const const GLVolume* v = selection.get_volume(*idxs.begin()); #if ENABLE_MODELVOLUME_TRANSFORM transform = v->world_matrix(); -#else - transform = v->world_matrix().cast(); -#endif // ENABLE_MODELVOLUME_TRANSFORM - // gets angles from first selected volume -#if ENABLE_MODELVOLUME_TRANSFORM angles = v->get_instance_rotation(); -#else - angles = v->get_rotation(); -#endif // ENABLE_MODELVOLUME_TRANSFORM - // consider rotation+mirror only components of the transform for offsets -#if ENABLE_MODELVOLUME_TRANSFORM offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); #else + transform = v->world_matrix().cast(); + // gets angles from first selected volume + angles = v->get_rotation(); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror()); +#endif // ENABLE_MODELVOLUME_TRANSFORM + } + else if (single_volume) + { + const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); + box = v->bounding_box; +#if ENABLE_MODELVOLUME_TRANSFORM + transform = v->world_matrix(); + angles = Geometry::extract_euler_angles(transform); + // consider rotation+mirror only components of the transform for offsets + offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_instance_mirror()); +#else + transform = v->world_matrix().cast(); + angles = Geometry::extract_euler_angles(transform); + // consider rotation+mirror only components of the transform for offsets offsets_transform = Geometry::assemble_transform(Vec3d::Zero(), angles, Vec3d::Ones(), v->get_mirror()); #endif // ENABLE_MODELVOLUME_TRANSFORM } From c227dad8cc56b2dc3e5a51eb5d468f885c2698fc Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 12 Nov 2018 13:47:24 +0100 Subject: [PATCH 2/6] Changed object list behavior when we have only one part(volume) inside main object --- src/slic3r/GUI/GUI_ObjectList.cpp | 19 ++++++- src/slic3r/GUI/GUI_ObjectList.hpp | 2 + src/slic3r/GUI/wxExtensions.cpp | 85 +++++++++++++++++++++++++++++-- src/slic3r/GUI/wxExtensions.hpp | 8 +++ 4 files changed, 108 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 306a546c5..106ea2d16 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -71,6 +71,8 @@ ObjectList::ObjectList(wxWindow* parent) : Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [this](wxDataViewEvent& e) {on_begin_drag(e); }); Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, [this](wxDataViewEvent& e) {on_drop_possible(e); }); Bind(wxEVT_DATAVIEW_ITEM_DROP, [this](wxDataViewEvent& e) {on_drop(e); }); + + Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED,[this](wxCommandEvent& e) {last_volume_is_deleted(e.GetInt()); }); } ObjectList::~ObjectList() @@ -88,6 +90,7 @@ void ObjectList::create_objects_ctrl() m_objects_model = new PrusaObjectDataViewModel; AssociateModel(m_objects_model); + m_objects_model->SetAssociatedControl(this); #if wxUSE_DRAG_AND_DROP && wxUSE_UNICODE EnableDragSource(wxDF_UNICODETEXT); EnableDropTarget(wxDF_UNICODETEXT); @@ -96,7 +99,7 @@ void ObjectList::create_objects_ctrl() // column 0(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps AppendColumn(new wxDataViewColumn(_(L("Name")), new PrusaBitmapTextRenderer(), - 0, 250, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); + 0, 200, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column 1 of the view control: AppendColumn(create_objects_list_extruder_column(4)); @@ -1434,5 +1437,19 @@ void ObjectList::change_part_type() } } +void ObjectList::last_volume_is_deleted(const int obj_idx) +{ + + if (obj_idx < 0 || (*m_objects).empty() || (*m_objects)[obj_idx]->volumes.empty()) + return; + auto volume = (*m_objects)[obj_idx]->volumes[0]; + + // clear volume's config values + volume->config.clear(); + + // set a default extruder value, since user can't add it manually + volume->config.set_key_value("extruder", new ConfigOptionInt(0)); +} + } //namespace GUI } //namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index d5f4848ad..dd0b27bd7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -149,6 +149,8 @@ public: ModelVolume* get_selected_model_volume(); void change_part_type(); + + void last_volume_is_deleted(const int obj_idx); }; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index bf3857870..6d68d4f02 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -12,6 +12,7 @@ #include "Model.hpp" wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); +wxDEFINE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description, std::function cb, const std::string& icon, wxEvtHandler* event_handler) @@ -564,12 +565,22 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) // NOTE: MyObjectTreeModelNodePtrArray is only an array of _pointers_ // thus removing the node from it doesn't result in freeing it if (node_parent) { + if (node->m_type == itInstanceRoot) + { + for (int i = node->GetChildCount() - 1; i > 0; i--) + Delete(wxDataViewItem(node->GetNthChild(i))); + return parent; + } + auto id = node_parent->GetChildren().Index(node); auto idx = node->GetIdx(); - node_parent->GetChildren().Remove(node); - if (node->m_type == itVolume) + + if (node->m_type == itVolume) { node_parent->m_volumes_cnt--; + DeleteSettings(item); + } + node_parent->GetChildren().Remove(node); if (id > 0) { if(id == node_parent->GetChildCount()) id--; @@ -600,21 +611,69 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) obj_node->GetChildren().Remove(node_parent); delete node_parent; ret_item = wxDataViewItem(obj_node); - ItemDeleted(ret_item, wxDataViewItem(node_parent)); #ifndef __WXGTK__ if (obj_node->GetChildCount() == 0) obj_node->m_container = false; #endif //__WXGTK__ + ItemDeleted(ret_item, wxDataViewItem(node_parent)); return ret_item; } + + // if there is last volume item after deleting, delete this last volume too + if (node_parent->GetChildCount() <= 3) + { + int vol_cnt = 0; + int vol_idx = 0; + for (int i = 0; i < node_parent->GetChildCount(); ++i) { + if (node_parent->GetNthChild(i)->GetType() == itVolume) { + vol_idx = i; + vol_cnt++; + } + if (vol_cnt > 1) + break; + } + + if (vol_cnt == 1) { + delete node; + ItemDeleted(parent, item); + + PrusaObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); + DeleteSettings(wxDataViewItem(last_child_node)); + node_parent->GetChildren().Remove(last_child_node); + delete last_child_node; + +#ifndef __WXGTK__ + if (node_parent->GetChildCount() == 0) + node_parent->m_container = false; +#endif //__WXGTK__ + ItemDeleted(parent, wxDataViewItem(last_child_node)); + + wxCommandEvent event(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED); + auto it = find(m_objects.begin(), m_objects.end(), node_parent); + event.SetInt(it == m_objects.end() ? -1 : it - m_objects.begin()); + wxPostEvent(m_ctrl, event); + + ret_item = parent; + + return ret_item; + } + } } else { auto it = find(m_objects.begin(), m_objects.end(), node); auto id = it - m_objects.begin(); if (it != m_objects.end()) + { + // Delete all sub-items + int i = m_objects[id]->GetChildCount() - 1; + while (i >= 0) { + Delete(wxDataViewItem(m_objects[id]->GetNthChild(i))); + i = m_objects[id]->GetChildCount() - 1; + } m_objects.erase(it); + } if (id > 0) { if(id == m_objects.size()) id--; ret_item = wxDataViewItem(m_objects[id]); @@ -733,8 +792,8 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) continue; auto item = wxDataViewItem(node); + DeleteSettings(item); children.RemoveAt(id); - root->m_volumes_cnt--; // free the node delete node; @@ -742,6 +801,7 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) // notify control ItemDeleted(parent, item); } + root->m_volumes_cnt = 0; // set m_containet to FALSE if parent has no child #ifndef __WXGTK__ @@ -749,6 +809,21 @@ void PrusaObjectDataViewModel::DeleteVolumeChildren(wxDataViewItem& parent) #endif //__WXGTK__ } +void PrusaObjectDataViewModel::DeleteSettings(const wxDataViewItem& parent) +{ + PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)parent.GetID(); + if (!node) return; + + // if volume has a "settings"item, than delete it before volume deleting + if (node->GetChildCount() > 0 && node->GetNthChild(0)->GetType() == itSettings) { + auto settings_node = node->GetNthChild(0); + auto settings_item = wxDataViewItem(settings_node); + node->GetChildren().RemoveAt(0); + delete settings_node; + ItemDeleted(parent, settings_item); + } +} + wxDataViewItem PrusaObjectDataViewModel::GetItemById(int obj_idx) { if (obj_idx >= m_objects.size()) @@ -841,7 +916,7 @@ void PrusaObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type = itUndef; PrusaObjectDataViewModelNode *node = (PrusaObjectDataViewModelNode*)item.GetID(); - if (!node || node->GetIdx() < 0 && !(node->GetType() & (itObject|itSettings|itInstanceRoot))) + if (!node || node->GetIdx() <-1 || node->GetIdx() ==-1 && !(node->GetType() & (itObject | itSettings | itInstanceRoot))) return; idx = node->GetIdx(); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index e5e4600da..d9f996b27 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -422,11 +422,16 @@ private: // PrusaObjectDataViewModel // ---------------------------------------------------------------------------- +// custom message the model sends to associated control to notify a last volume deleted from the object: +wxDECLARE_EVENT(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, wxCommandEvent); + class PrusaObjectDataViewModel :public wxDataViewModel { std::vector m_objects; std::vector m_volume_bmps; + wxDataViewCtrl* m_ctrl{ nullptr }; + public: PrusaObjectDataViewModel(); ~PrusaObjectDataViewModel(); @@ -444,6 +449,7 @@ public: void DeleteAll(); void DeleteChildren(wxDataViewItem& parent); void DeleteVolumeChildren(wxDataViewItem& parent); + void DeleteSettings(const wxDataViewItem& parent); wxDataViewItem GetItemById(int obj_idx); wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); @@ -500,6 +506,8 @@ public: void SetVolumeBitmaps(const std::vector& volume_bmps) { m_volume_bmps = volume_bmps; } void SetVolumeType(const wxDataViewItem &item, const int type); + + void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } }; // ---------------------------------------------------------------------------- From a4e1ab2281aa78fb7ff4d5d7a24cc32d7d94e21a Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 12 Nov 2018 14:52:52 +0100 Subject: [PATCH 3/6] Getting rid of AppController. --- src/libslic3r/CMakeLists.txt | 1 + src/libslic3r/ModelArrange.cpp | 763 +++++++++++++++++++++++++++++++++ src/libslic3r/ModelArrange.hpp | 756 +------------------------------- src/slic3r/AppController.cpp | 364 ---------------- src/slic3r/AppController.hpp | 414 ------------------ src/slic3r/AppControllerWx.cpp | 340 --------------- src/slic3r/CMakeLists.txt | 3 - src/slic3r/GUI/GUI.cpp | 20 - src/slic3r/GUI/MainFrame.cpp | 13 +- src/slic3r/GUI/MainFrame.hpp | 6 +- src/slic3r/GUI/Plater.cpp | 87 +++- xs/xsp/AppController.xsp | 29 -- xs/xsp/my.map | 2 - 13 files changed, 857 insertions(+), 1941 deletions(-) create mode 100644 src/libslic3r/ModelArrange.cpp delete mode 100644 src/slic3r/AppController.cpp delete mode 100644 src/slic3r/AppController.hpp delete mode 100644 src/slic3r/AppControllerWx.cpp delete mode 100644 xs/xsp/AppController.xsp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9da362302..952b11ed0 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -108,6 +108,7 @@ add_library(libslic3r STATIC Model.cpp Model.hpp ModelArrange.hpp + ModelArrange.cpp MotionPlanner.cpp MotionPlanner.hpp MultiPoint.cpp diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp new file mode 100644 index 000000000..9d59238fa --- /dev/null +++ b/src/libslic3r/ModelArrange.cpp @@ -0,0 +1,763 @@ +#include "ModelArrange.hpp" +#include "Model.hpp" +#include "SVG.hpp" + +#include + +#include +#include + +#include + +namespace Slic3r { + +namespace arr { + +using namespace libnest2d; + +std::string toString(const Model& model, bool holes = true) { + std::stringstream ss; + + ss << "{\n"; + + for(auto objptr : model.objects) { + if(!objptr) continue; + + auto rmesh = objptr->raw_mesh(); + + for(auto objinst : objptr->instances) { + if(!objinst) continue; + + Slic3r::TriangleMesh tmpmesh = rmesh; + // CHECK_ME -> Is the following correct ? + tmpmesh.scale(objinst->get_scaling_factor()); + objinst->transform_mesh(&tmpmesh); + ExPolygons expolys = tmpmesh.horizontal_projection(); + for(auto& expoly_complex : expolys) { + + auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); + if(tmp.empty()) continue; + auto expoly = tmp.front(); + expoly.contour.make_clockwise(); + for(auto& h : expoly.holes) h.make_counter_clockwise(); + + ss << "\t{\n"; + ss << "\t\t{\n"; + + for(auto v : expoly.contour.points) ss << "\t\t\t{" + << v(0) << ", " + << v(1) << "},\n"; + { + auto v = expoly.contour.points.front(); + ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; + } + ss << "\t\t},\n"; + + // Holes: + ss << "\t\t{\n"; + if(holes) for(auto h : expoly.holes) { + ss << "\t\t\t{\n"; + for(auto v : h.points) ss << "\t\t\t\t{" + << v(0) << ", " + << v(1) << "},\n"; + { + auto v = h.points.front(); + ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; + } + ss << "\t\t\t},\n"; + } + ss << "\t\t},\n"; + + ss << "\t},\n"; + } + } + } + + ss << "}\n"; + + return ss.str(); +} + +void toSVG(SVG& svg, const Model& model) { + for(auto objptr : model.objects) { + if(!objptr) continue; + + auto rmesh = objptr->raw_mesh(); + + for(auto objinst : objptr->instances) { + if(!objinst) continue; + + Slic3r::TriangleMesh tmpmesh = rmesh; + tmpmesh.scale(objinst->get_scaling_factor()); + objinst->transform_mesh(&tmpmesh); + ExPolygons expolys = tmpmesh.horizontal_projection(); + svg.draw(expolys); + } + } +} + +namespace bgi = boost::geometry::index; + +using SpatElement = std::pair; +using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; +using ItemGroup = std::vector>; +template +using TPacker = typename placers::_NofitPolyPlacer; + +const double BIG_ITEM_TRESHOLD = 0.02; + +Box boundingBox(const Box& pilebb, const Box& ibb ) { + auto& pminc = pilebb.minCorner(); + auto& pmaxc = pilebb.maxCorner(); + auto& iminc = ibb.minCorner(); + auto& imaxc = ibb.maxCorner(); + PointImpl minc, maxc; + + setX(minc, std::min(getX(pminc), getX(iminc))); + setY(minc, std::min(getY(pminc), getY(iminc))); + + setX(maxc, std::max(getX(pmaxc), getX(imaxc))); + setY(maxc, std::max(getY(pmaxc), getY(imaxc))); + return Box(minc, maxc); +} + +std::tuple +objfunc(const PointImpl& bincenter, + const shapelike::Shapes& merged_pile, + const Box& pilebb, + const ItemGroup& items, + const Item &item, + double bin_area, + double norm, // A norming factor for physical dimensions + // a spatial index to quickly get neighbors of the candidate item + const SpatIndex& spatindex, + const SpatIndex& smalls_spatindex, + const ItemGroup& remaining + ) +{ + using Coord = TCoord; + + static const double ROUNDNESS_RATIO = 0.5; + static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; + + // We will treat big items (compared to the print bed) differently + auto isBig = [bin_area](double a) { + return a/bin_area > BIG_ITEM_TRESHOLD ; + }; + + // Candidate item bounding box + auto ibb = sl::boundingBox(item.transformedShape()); + + // Calculate the full bounding box of the pile with the candidate item + auto fullbb = boundingBox(pilebb, ibb); + + // The bounding box of the big items (they will accumulate in the center + // of the pile + Box bigbb; + if(spatindex.empty()) bigbb = fullbb; + else { + auto boostbb = spatindex.bounds(); + boost::geometry::convert(boostbb, bigbb); + } + + // Will hold the resulting score + double score = 0; + + if(isBig(item.area()) || spatindex.empty()) { + // This branch is for the bigger items.. + + auto minc = ibb.minCorner(); // bottom left corner + auto maxc = ibb.maxCorner(); // top right corner + + // top left and bottom right corners + auto top_left = PointImpl{getX(minc), getY(maxc)}; + auto bottom_right = PointImpl{getX(maxc), getY(minc)}; + + // Now the distance of the gravity center will be calculated to the + // five anchor points and the smallest will be chosen. + std::array dists; + auto cc = fullbb.center(); // The gravity center + dists[0] = pl::distance(minc, cc); + dists[1] = pl::distance(maxc, cc); + dists[2] = pl::distance(ibb.center(), cc); + dists[3] = pl::distance(top_left, cc); + dists[4] = pl::distance(bottom_right, cc); + + // The smalles distance from the arranged pile center: + auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; + auto bindist = pl::distance(ibb.center(), bincenter) / norm; + dist = 0.8*dist + 0.2*bindist; + + // Density is the pack density: how big is the arranged pile + double density = 0; + + if(remaining.empty()) { + + auto mp = merged_pile; + mp.emplace_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + + placers::EdgeCache ec(chull); + + double circ = ec.circumference() / norm; + double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; + score = 0.5*circ + 0.5*bcirc; + + } else { + // Prepare a variable for the alignment score. + // This will indicate: how well is the candidate item aligned with + // its neighbors. We will check the alignment with all neighbors and + // return the score for the best alignment. So it is enough for the + // candidate to be aligned with only one item. + auto alignment_score = 1.0; + + density = std::sqrt((fullbb.width() / norm )* + (fullbb.height() / norm)); + auto querybb = item.boundingBox(); + + // Query the spatial index for the neighbors + std::vector result; + result.reserve(spatindex.size()); + if(isBig(item.area())) { + spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } else { + smalls_spatindex.query(bgi::intersects(querybb), + std::back_inserter(result)); + } + + for(auto& e : result) { // now get the score for the best alignment + auto idx = e.second; + Item& p = items[idx]; + auto parea = p.area(); + if(std::abs(1.0 - parea/item.area()) < 1e-6) { + auto bb = boundingBox(p.boundingBox(), ibb); + auto bbarea = bb.area(); + auto ascore = 1.0 - (item.area() + parea)/bbarea; + + if(ascore < alignment_score) alignment_score = ascore; + } + } + + // The final mix of the score is the balance between the distance + // from the full pile center, the pack density and the + // alignment with the neighbors + if(result.empty()) + score = 0.5 * dist + 0.5 * density; + else + score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; + } + } else { + // Here there are the small items that should be placed around the + // already processed bigger items. + // No need to play around with the anchor points, the center will be + // just fine for small items + score = pl::distance(ibb.center(), bigbb.center()) / norm; + } + + return std::make_tuple(score, fullbb); +} + +template +void fillConfig(PConf& pcfg) { + + // Align the arranged pile into the center of the bin + pcfg.alignment = PConf::Alignment::CENTER; + + // Start placing the items from the center of the print bed + pcfg.starting_point = PConf::Alignment::CENTER; + + // TODO cannot use rotations until multiple objects of same geometry can + // handle different rotations + // arranger.useMinimumBoundigBoxRotation(); + pcfg.rotations = { 0.0 }; + + // The accuracy of optimization. + // Goes from 0.0 to 1.0 and scales performance as well + pcfg.accuracy = 0.65f; + + pcfg.parallel = true; +} + +template +class AutoArranger {}; + +template +class _ArrBase { +protected: + + using Placer = TPacker; + using Selector = FirstFitSelection; + using Packer = Nester; + using PConfig = typename Packer::PlacementConfig; + using Distance = TCoord; + using Pile = sl::Shapes; + + Packer m_pck; + PConfig m_pconf; // Placement configuration + double m_bin_area; + SpatIndex m_rtree; + SpatIndex m_smallsrtree; + double m_norm; + Pile m_merged_pile; + Box m_pilebb; + ItemGroup m_remaining; + ItemGroup m_items; +public: + + _ArrBase(const TBin& bin, Distance dist, + std::function progressind, + std::function stopcond): + m_pck(bin, dist), m_bin_area(sl::area(bin)), + m_norm(std::sqrt(sl::area(bin))) + { + fillConfig(m_pconf); + + m_pconf.before_packing = + [this](const Pile& merged_pile, // merged pile + const ItemGroup& items, // packed items + const ItemGroup& remaining) // future items to be packed + { + m_items = items; + m_merged_pile = merged_pile; + m_remaining = remaining; + + m_pilebb = sl::boundingBox(merged_pile); + + m_rtree.clear(); + m_smallsrtree.clear(); + + // We will treat big items (compared to the print bed) differently + auto isBig = [this](double a) { + return a/m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + for(unsigned idx = 0; idx < items.size(); ++idx) { + Item& itm = items[idx]; + if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); + m_smallsrtree.insert({itm.boundingBox(), idx}); + } + }; + + m_pck.progressIndicator(progressind); + m_pck.stopCondition(stopcond); + } + + template inline IndexedPackGroup operator()(Args&&...args) { + m_rtree.clear(); + return m_pck.executeIndexed(std::forward(args)...); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const Box& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) + { + + m_pconf.object_function = [this, bin] (const Item &item) { + + auto result = objfunc(bin.center(), + m_merged_pile, + m_pilebb, + m_items, + item, + m_bin_area, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + + double score = std::get<0>(result); + auto& fullbb = std::get<1>(result); + + double miss = Placer::overfit(fullbb, bin); + miss = miss > 0? miss : 0; + score += miss*miss; + + return score; + }; + + m_pck.configure(m_pconf); + } +}; + +using lnCircle = libnest2d::_Circle; + +inline lnCircle to_lnCircle(const Circle& circ) { + return lnCircle({circ.center()(0), circ.center()(1)}, circ.radius()); +} + +template<> +class AutoArranger: public _ArrBase { +public: + + AutoArranger(const lnCircle& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) { + + m_pconf.object_function = [this, &bin] (const Item &item) { + + auto result = objfunc(bin.center(), + m_merged_pile, + m_pilebb, + m_items, + item, + m_bin_area, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + + double score = std::get<0>(result); + + auto isBig = [this](const Item& itm) { + return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; + }; + + if(isBig(item)) { + auto mp = m_merged_pile; + mp.push_back(item.transformedShape()); + auto chull = sl::convexHull(mp); + double miss = Placer::overfit(chull, bin); + if(miss < 0) miss = 0; + score += miss*miss; + } + + return score; + }; + + m_pck.configure(m_pconf); + } +}; + +template<> +class AutoArranger: public _ArrBase { +public: + AutoArranger(const PolygonImpl& bin, Distance dist, + std::function progressind, + std::function stopcond): + _ArrBase(bin, dist, progressind, stopcond) + { + m_pconf.object_function = [this, &bin] (const Item &item) { + + auto binbb = sl::boundingBox(bin); + auto result = objfunc(binbb.center(), + m_merged_pile, + m_pilebb, + m_items, + item, + m_bin_area, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + double score = std::get<0>(result); + + return score; + }; + + m_pck.configure(m_pconf); + } +}; + +template<> // Specialization with no bin +class AutoArranger: public _ArrBase { +public: + + AutoArranger(Distance dist, std::function progressind, + std::function stopcond): + _ArrBase(Box(0, 0), dist, progressind, stopcond) + { + this->m_pconf.object_function = [this] (const Item &item) { + + auto result = objfunc({0, 0}, + m_merged_pile, + m_pilebb, + m_items, + item, + 0, + m_norm, + m_rtree, + m_smallsrtree, + m_remaining); + return std::get<0>(result); + }; + + this->m_pck.configure(m_pconf); + } +}; + +// A container which stores a pointer to the 3D object and its projected +// 2D shape from top view. +using ShapeData2D = + std::vector>; + +ShapeData2D projectModelFromTop(const Slic3r::Model &model) { + ShapeData2D ret; + + auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0), + [](size_t s, ModelObject* o){ + return s + o->instances.size(); + }); + + ret.reserve(s); + + for(auto objptr : model.objects) { + if(objptr) { + + auto rmesh = objptr->raw_mesh(); + + for(auto objinst : objptr->instances) { + if(objinst) { + Slic3r::TriangleMesh tmpmesh = rmesh; + ClipperLib::PolygonImpl pn; + + // CHECK_ME -> is the following correct ? + tmpmesh.scale(objinst->get_scaling_factor()); + + // TODO export the exact 2D projection + auto p = tmpmesh.convex_hull(); + + p.make_clockwise(); + p.append(p.first_point()); + pn.Contour = Slic3rMultiPoint_to_ClipperPath( p ); + + // Efficient conversion to item. + Item item(std::move(pn)); + + // Invalid geometries would throw exceptions when arranging + if(item.vertexCount() > 3) { + // CHECK_ME -> is the following correct or it should take in account all three rotations ? + item.rotation(objinst->get_rotation(Z)); + item.translation({ + ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), + ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) + }); + ret.emplace_back(objinst, item); + } + } + } + } + } + + return ret; +} + +void applyResult( + IndexedPackGroup::value_type& group, + Coord batch_offset, + ShapeData2D& shapemap) +{ + for(auto& r : group) { + auto idx = r.first; // get the original item index + Item& item = r.second; // get the item itself + + // Get the model instance from the shapemap using the index + ModelInstance *inst_ptr = shapemap[idx].first; + + // Get the transformation data from the item object and scale it + // appropriately + auto off = item.translation(); + Radians rot = item.rotation(); + Vec3d foff(off.X*SCALING_FACTOR + batch_offset, + off.Y*SCALING_FACTOR, + inst_ptr->get_offset()(2)); + + // write the transformation data into the model instance + inst_ptr->set_rotation(Z, rot); + inst_ptr->set_offset(foff); + } +} + +BedShapeHint bedShape(const Polyline &bed) { + BedShapeHint ret; + + auto x = [](const Point& p) { return p(0); }; + auto y = [](const Point& p) { return p(1); }; + + auto width = [x](const BoundingBox& box) { + return x(box.max) - x(box.min); + }; + + auto height = [y](const BoundingBox& box) { + return y(box.max) - y(box.min); + }; + + auto area = [&width, &height](const BoundingBox& box) { + double w = width(box); + double h = height(box); + return w*h; + }; + + auto poly_area = [](Polyline p) { + Polygon pp; pp.points.reserve(p.points.size() + 1); + pp.points = std::move(p.points); + pp.points.emplace_back(pp.points.front()); + return std::abs(pp.area()); + }; + + auto distance_to = [x, y](const Point& p1, const Point& p2) { + double dx = x(p2) - x(p1); + double dy = y(p2) - y(p1); + return std::sqrt(dx*dx + dy*dy); + }; + + auto bb = bed.bounding_box(); + + auto isCircle = [bb, distance_to](const Polyline& polygon) { + auto center = bb.center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt: polygon.points) + { + double distance = distance_to(center, pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + Circle ret(center, avg_dist); + for (auto el: vertex_distances) + { + if (abs(el - avg_dist) > 10 * SCALED_EPSILON) + ret = Circle(); + break; + } + + return ret; + }; + + auto parea = poly_area(bed); + + if( (1.0 - parea/area(bb)) < 1e-3 ) { + ret.type = BedShapeType::BOX; + ret.shape.box = bb; + } + else if(auto c = isCircle(bed)) { + ret.type = BedShapeType::CIRCLE; + ret.shape.circ = c; + } else { + ret.type = BedShapeType::IRREGULAR; + ret.shape.polygon = bed; + } + + // Determine the bed shape by hand + return ret; +} + +bool arrange(Model &model, + coord_t min_obj_distance, + const Polyline &bed, + BedShapeHint bedhint, + bool first_bin_only, + std::function progressind, + std::function stopcondition) +{ + using ArrangeResult = _IndexedPackGroup; + + bool ret = true; + + // Get the 2D projected shapes with their 3D model instance pointers + auto shapemap = arr::projectModelFromTop(model); + + // Copy the references for the shapes only as the arranger expects a + // sequence of objects convertible to Item or ClipperPolygon + std::vector> shapes; + shapes.reserve(shapemap.size()); + std::for_each(shapemap.begin(), shapemap.end(), + [&shapes] (ShapeData2D::value_type& it) + { + shapes.push_back(std::ref(it.second)); + }); + + IndexedPackGroup result; + + // If there is no hint about the shape, we will try to guess + if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); + + BoundingBox bbb(bed); + + auto& cfn = stopcondition; + + auto binbb = Box({ + static_cast(bbb.min(0)), + static_cast(bbb.min(1)) + }, + { + static_cast(bbb.max(0)), + static_cast(bbb.max(1)) + }); + + switch(bedhint.type) { + case BedShapeType::BOX: { + + // Create the arranger for the box shaped bed + AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + case BedShapeType::CIRCLE: { + + auto c = bedhint.shape.circ; + auto cc = to_lnCircle(c); + + AutoArranger arrange(cc, min_obj_distance, progressind, cfn); + result = arrange(shapes.begin(), shapes.end()); + break; + } + case BedShapeType::IRREGULAR: + case BedShapeType::WHO_KNOWS: { + + using P = libnest2d::PolygonImpl; + + auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); + P irrbed = sl::create(std::move(ctour)); + + AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); + + // Arrange and return the items with their respective indices within the + // input sequence. + result = arrange(shapes.begin(), shapes.end()); + break; + } + }; + + if(result.empty() || stopcondition()) return false; + + if(first_bin_only) { + applyResult(result.front(), 0, shapemap); + } else { + + const auto STRIDE_PADDING = 1.2; + + Coord stride = static_cast(STRIDE_PADDING* + binbb.width()*SCALING_FACTOR); + Coord batch_offset = 0; + + for(auto& group : result) { + applyResult(group, batch_offset, shapemap); + + // Only the first pack group can be placed onto the print bed. The + // other objects which could not fit will be placed next to the + // print bed + batch_offset += stride; + } + } + + for(auto objptr : model.objects) objptr->invalidate_bounding_box(); + + return ret && result.size() == 1; +} + +} +} diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 380095024..78a400667 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -2,549 +2,13 @@ #define MODELARRANGE_HPP #include "Model.hpp" -#include "SVG.hpp" -#include - -#include -#include - -#include namespace Slic3r { + +class Model; + namespace arr { -using namespace libnest2d; - -std::string toString(const Model& model, bool holes = true) { - std::stringstream ss; - - ss << "{\n"; - - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - // CHECK_ME -> Is the following correct ? - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - for(auto& expoly_complex : expolys) { - - auto tmp = expoly_complex.simplify(1.0/SCALING_FACTOR); - if(tmp.empty()) continue; - auto expoly = tmp.front(); - expoly.contour.make_clockwise(); - for(auto& h : expoly.holes) h.make_counter_clockwise(); - - ss << "\t{\n"; - ss << "\t\t{\n"; - - for(auto v : expoly.contour.points) ss << "\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = expoly.contour.points.front(); - ss << "\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t},\n"; - - // Holes: - ss << "\t\t{\n"; - if(holes) for(auto h : expoly.holes) { - ss << "\t\t\t{\n"; - for(auto v : h.points) ss << "\t\t\t\t{" - << v(0) << ", " - << v(1) << "},\n"; - { - auto v = h.points.front(); - ss << "\t\t\t\t{" << v(0) << ", " << v(1) << "},\n"; - } - ss << "\t\t\t},\n"; - } - ss << "\t\t},\n"; - - ss << "\t},\n"; - } - } - } - - ss << "}\n"; - - return ss.str(); -} - -void toSVG(SVG& svg, const Model& model) { - for(auto objptr : model.objects) { - if(!objptr) continue; - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(!objinst) continue; - - Slic3r::TriangleMesh tmpmesh = rmesh; - tmpmesh.scale(objinst->get_scaling_factor()); - objinst->transform_mesh(&tmpmesh); - ExPolygons expolys = tmpmesh.horizontal_projection(); - svg.draw(expolys); - } - } -} - -namespace bgi = boost::geometry::index; - -using SpatElement = std::pair; -using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; -using ItemGroup = std::vector>; -template -using TPacker = typename placers::_NofitPolyPlacer; - -const double BIG_ITEM_TRESHOLD = 0.02; - -Box boundingBox(const Box& pilebb, const Box& ibb ) { - auto& pminc = pilebb.minCorner(); - auto& pmaxc = pilebb.maxCorner(); - auto& iminc = ibb.minCorner(); - auto& imaxc = ibb.maxCorner(); - PointImpl minc, maxc; - - setX(minc, std::min(getX(pminc), getX(iminc))); - setY(minc, std::min(getY(pminc), getY(iminc))); - - setX(maxc, std::max(getX(pmaxc), getX(imaxc))); - setY(maxc, std::max(getY(pmaxc), getY(imaxc))); - return Box(minc, maxc); -} - -std::tuple -objfunc(const PointImpl& bincenter, - const shapelike::Shapes& merged_pile, - const Box& pilebb, - const ItemGroup& items, - const Item &item, - double bin_area, - double norm, // A norming factor for physical dimensions - // a spatial index to quickly get neighbors of the candidate item - const SpatIndex& spatindex, - const SpatIndex& smalls_spatindex, - const ItemGroup& remaining - ) -{ - using Coord = TCoord; - - static const double ROUNDNESS_RATIO = 0.5; - static const double DENSITY_RATIO = 1.0 - ROUNDNESS_RATIO; - - // We will treat big items (compared to the print bed) differently - auto isBig = [bin_area](double a) { - return a/bin_area > BIG_ITEM_TRESHOLD ; - }; - - // Candidate item bounding box - auto ibb = sl::boundingBox(item.transformedShape()); - - // Calculate the full bounding box of the pile with the candidate item - auto fullbb = boundingBox(pilebb, ibb); - - // The bounding box of the big items (they will accumulate in the center - // of the pile - Box bigbb; - if(spatindex.empty()) bigbb = fullbb; - else { - auto boostbb = spatindex.bounds(); - boost::geometry::convert(boostbb, bigbb); - } - - // Will hold the resulting score - double score = 0; - - if(isBig(item.area()) || spatindex.empty()) { - // This branch is for the bigger items.. - - auto minc = ibb.minCorner(); // bottom left corner - auto maxc = ibb.maxCorner(); // top right corner - - // top left and bottom right corners - auto top_left = PointImpl{getX(minc), getY(maxc)}; - auto bottom_right = PointImpl{getX(maxc), getY(minc)}; - - // Now the distance of the gravity center will be calculated to the - // five anchor points and the smallest will be chosen. - std::array dists; - auto cc = fullbb.center(); // The gravity center - dists[0] = pl::distance(minc, cc); - dists[1] = pl::distance(maxc, cc); - dists[2] = pl::distance(ibb.center(), cc); - dists[3] = pl::distance(top_left, cc); - dists[4] = pl::distance(bottom_right, cc); - - // The smalles distance from the arranged pile center: - auto dist = *(std::min_element(dists.begin(), dists.end())) / norm; - auto bindist = pl::distance(ibb.center(), bincenter) / norm; - dist = 0.8*dist + 0.2*bindist; - - // Density is the pack density: how big is the arranged pile - double density = 0; - - if(remaining.empty()) { - - auto mp = merged_pile; - mp.emplace_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - - placers::EdgeCache ec(chull); - - double circ = ec.circumference() / norm; - double bcirc = 2.0*(fullbb.width() + fullbb.height()) / norm; - score = 0.5*circ + 0.5*bcirc; - - } else { - // Prepare a variable for the alignment score. - // This will indicate: how well is the candidate item aligned with - // its neighbors. We will check the alignment with all neighbors and - // return the score for the best alignment. So it is enough for the - // candidate to be aligned with only one item. - auto alignment_score = 1.0; - - density = std::sqrt((fullbb.width() / norm )* - (fullbb.height() / norm)); - auto querybb = item.boundingBox(); - - // Query the spatial index for the neighbors - std::vector result; - result.reserve(spatindex.size()); - if(isBig(item.area())) { - spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } else { - smalls_spatindex.query(bgi::intersects(querybb), - std::back_inserter(result)); - } - - for(auto& e : result) { // now get the score for the best alignment - auto idx = e.second; - Item& p = items[idx]; - auto parea = p.area(); - if(std::abs(1.0 - parea/item.area()) < 1e-6) { - auto bb = boundingBox(p.boundingBox(), ibb); - auto bbarea = bb.area(); - auto ascore = 1.0 - (item.area() + parea)/bbarea; - - if(ascore < alignment_score) alignment_score = ascore; - } - } - - // The final mix of the score is the balance between the distance - // from the full pile center, the pack density and the - // alignment with the neighbors - if(result.empty()) - score = 0.5 * dist + 0.5 * density; - else - score = 0.40 * dist + 0.40 * density + 0.2 * alignment_score; - } - } else { - // Here there are the small items that should be placed around the - // already processed bigger items. - // No need to play around with the anchor points, the center will be - // just fine for small items - score = pl::distance(ibb.center(), bigbb.center()) / norm; - } - - return std::make_tuple(score, fullbb); -} - -template -void fillConfig(PConf& pcfg) { - - // Align the arranged pile into the center of the bin - pcfg.alignment = PConf::Alignment::CENTER; - - // Start placing the items from the center of the print bed - pcfg.starting_point = PConf::Alignment::CENTER; - - // TODO cannot use rotations until multiple objects of same geometry can - // handle different rotations - // arranger.useMinimumBoundigBoxRotation(); - pcfg.rotations = { 0.0 }; - - // The accuracy of optimization. - // Goes from 0.0 to 1.0 and scales performance as well - pcfg.accuracy = 0.65f; - - pcfg.parallel = true; -} - -template -class AutoArranger {}; - -template -class _ArrBase { -protected: - - using Placer = TPacker; - using Selector = FirstFitSelection; - using Packer = Nester; - using PConfig = typename Packer::PlacementConfig; - using Distance = TCoord; - using Pile = sl::Shapes; - - Packer m_pck; - PConfig m_pconf; // Placement configuration - double m_bin_area; - SpatIndex m_rtree; - SpatIndex m_smallsrtree; - double m_norm; - Pile m_merged_pile; - Box m_pilebb; - ItemGroup m_remaining; - ItemGroup m_items; -public: - - _ArrBase(const TBin& bin, Distance dist, - std::function progressind, - std::function stopcond): - m_pck(bin, dist), m_bin_area(sl::area(bin)), - m_norm(std::sqrt(sl::area(bin))) - { - fillConfig(m_pconf); - - m_pconf.before_packing = - [this](const Pile& merged_pile, // merged pile - const ItemGroup& items, // packed items - const ItemGroup& remaining) // future items to be packed - { - m_items = items; - m_merged_pile = merged_pile; - m_remaining = remaining; - - m_pilebb = sl::boundingBox(merged_pile); - - m_rtree.clear(); - m_smallsrtree.clear(); - - // We will treat big items (compared to the print bed) differently - auto isBig = [this](double a) { - return a/m_bin_area > BIG_ITEM_TRESHOLD ; - }; - - for(unsigned idx = 0; idx < items.size(); ++idx) { - Item& itm = items[idx]; - if(isBig(itm.area())) m_rtree.insert({itm.boundingBox(), idx}); - m_smallsrtree.insert({itm.boundingBox(), idx}); - } - }; - - m_pck.progressIndicator(progressind); - m_pck.stopCondition(stopcond); - } - - template inline IndexedPackGroup operator()(Args&&...args) { - m_rtree.clear(); - return m_pck.executeIndexed(std::forward(args)...); - } -}; - -template<> -class AutoArranger: public _ArrBase { -public: - - AutoArranger(const Box& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(bin, dist, progressind, stopcond) - { - - m_pconf.object_function = [this, bin] (const Item &item) { - - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - - double score = std::get<0>(result); - auto& fullbb = std::get<1>(result); - - double miss = Placer::overfit(fullbb, bin); - miss = miss > 0? miss : 0; - score += miss*miss; - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -using lnCircle = libnest2d::_Circle; - -template<> -class AutoArranger: public _ArrBase { -public: - - AutoArranger(const lnCircle& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(bin, dist, progressind, stopcond) { - - m_pconf.object_function = [this, &bin] (const Item &item) { - - auto result = objfunc(bin.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - - double score = std::get<0>(result); - - auto isBig = [this](const Item& itm) { - return itm.area()/m_bin_area > BIG_ITEM_TRESHOLD ; - }; - - if(isBig(item)) { - auto mp = m_merged_pile; - mp.push_back(item.transformedShape()); - auto chull = sl::convexHull(mp); - double miss = Placer::overfit(chull, bin); - if(miss < 0) miss = 0; - score += miss*miss; - } - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -template<> -class AutoArranger: public _ArrBase { -public: - AutoArranger(const PolygonImpl& bin, Distance dist, - std::function progressind, - std::function stopcond): - _ArrBase(bin, dist, progressind, stopcond) - { - m_pconf.object_function = [this, &bin] (const Item &item) { - - auto binbb = sl::boundingBox(bin); - auto result = objfunc(binbb.center(), - m_merged_pile, - m_pilebb, - m_items, - item, - m_bin_area, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - double score = std::get<0>(result); - - return score; - }; - - m_pck.configure(m_pconf); - } -}; - -template<> // Specialization with no bin -class AutoArranger: public _ArrBase { -public: - - AutoArranger(Distance dist, std::function progressind, - std::function stopcond): - _ArrBase(Box(0, 0), dist, progressind, stopcond) - { - this->m_pconf.object_function = [this] (const Item &item) { - - auto result = objfunc({0, 0}, - m_merged_pile, - m_pilebb, - m_items, - item, - 0, - m_norm, - m_rtree, - m_smallsrtree, - m_remaining); - return std::get<0>(result); - }; - - this->m_pck.configure(m_pconf); - } -}; - -// A container which stores a pointer to the 3D object and its projected -// 2D shape from top view. -using ShapeData2D = - std::vector>; - -ShapeData2D projectModelFromTop(const Slic3r::Model &model) { - ShapeData2D ret; - - auto s = std::accumulate(model.objects.begin(), model.objects.end(), size_t(0), - [](size_t s, ModelObject* o){ - return s + o->instances.size(); - }); - - ret.reserve(s); - - for(auto objptr : model.objects) { - if(objptr) { - - auto rmesh = objptr->raw_mesh(); - - for(auto objinst : objptr->instances) { - if(objinst) { - Slic3r::TriangleMesh tmpmesh = rmesh; - ClipperLib::PolygonImpl pn; - - // CHECK_ME -> is the following correct ? - tmpmesh.scale(objinst->get_scaling_factor()); - - // TODO export the exact 2D projection - auto p = tmpmesh.convex_hull(); - - p.make_clockwise(); - p.append(p.first_point()); - pn.Contour = Slic3rMultiPoint_to_ClipperPath( p ); - - // Efficient conversion to item. - Item item(std::move(pn)); - - // Invalid geometries would throw exceptions when arranging - if(item.vertexCount() > 3) { - // CHECK_ME -> is the following correct or it should take in account all three rotations ? - item.rotation(objinst->get_rotation(Z)); - item.translation({ - ClipperLib::cInt(objinst->get_offset(X)/SCALING_FACTOR), - ClipperLib::cInt(objinst->get_offset(Y)/SCALING_FACTOR) - }); - ret.emplace_back(objinst, item); - } - } - } - } - } - - return ret; -} - class Circle { Point center_; double radius_; @@ -556,9 +20,9 @@ public: inline double radius() const { return radius_; } inline const Point& center() const { return center_; } inline operator bool() { return !std::isnan(radius_); } - inline operator lnCircle() { - return lnCircle({center_(0), center_(1)}, radius_); - } +// inline operator lnCircle() { +// return lnCircle({center_(0), center_(1)}, radius_); +// } }; enum class BedShapeType { @@ -577,109 +41,7 @@ struct BedShapeHint { } shape; }; -BedShapeHint bedShape(const Polyline& bed) { - BedShapeHint ret; - - auto x = [](const Point& p) { return p(0); }; - auto y = [](const Point& p) { return p(1); }; - - auto width = [x](const BoundingBox& box) { - return x(box.max) - x(box.min); - }; - - auto height = [y](const BoundingBox& box) { - return y(box.max) - y(box.min); - }; - - auto area = [&width, &height](const BoundingBox& box) { - double w = width(box); - double h = height(box); - return w*h; - }; - - auto poly_area = [](Polyline p) { - Polygon pp; pp.points.reserve(p.points.size() + 1); - pp.points = std::move(p.points); - pp.points.emplace_back(pp.points.front()); - return std::abs(pp.area()); - }; - - auto distance_to = [x, y](const Point& p1, const Point& p2) { - double dx = x(p2) - x(p1); - double dy = y(p2) - y(p1); - return std::sqrt(dx*dx + dy*dy); - }; - - auto bb = bed.bounding_box(); - - auto isCircle = [bb, distance_to](const Polyline& polygon) { - auto center = bb.center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = distance_to(center, pt); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - Circle ret(center, avg_dist); - for (auto el: vertex_distances) - { - if (abs(el - avg_dist) > 10 * SCALED_EPSILON) - ret = Circle(); - break; - } - - return ret; - }; - - auto parea = poly_area(bed); - - if( (1.0 - parea/area(bb)) < 1e-3 ) { - ret.type = BedShapeType::BOX; - ret.shape.box = bb; - } - else if(auto c = isCircle(bed)) { - ret.type = BedShapeType::CIRCLE; - ret.shape.circ = c; - } else { - ret.type = BedShapeType::IRREGULAR; - ret.shape.polygon = bed; - } - - // Determine the bed shape by hand - return ret; -} - -void applyResult( - IndexedPackGroup::value_type& group, - Coord batch_offset, - ShapeData2D& shapemap) -{ - for(auto& r : group) { - auto idx = r.first; // get the original item index - Item& item = r.second; // get the item itself - - // Get the model instance from the shapemap using the index - ModelInstance *inst_ptr = shapemap[idx].first; - - // Get the transformation data from the item object and scale it - // appropriately - auto off = item.translation(); - Radians rot = item.rotation(); - Vec3d foff(off.X*SCALING_FACTOR + batch_offset, - off.Y*SCALING_FACTOR, - inst_ptr->get_offset()(2)); - - // write the transformation data into the model instance - inst_ptr->set_rotation(Z, rot); - inst_ptr->set_offset(foff); - } -} - +BedShapeHint bedShape(const Polyline& bed); /** * \brief Arranges the model objects on the screen. @@ -707,112 +69,14 @@ void applyResult( * packed. The unsigned argument is the number of items remaining to pack. * \param stopcondition A predicate returning true if abort is needed. */ -bool arrange(Model &model, coordf_t min_obj_distance, +bool arrange(Model &model, coord_t min_obj_distance, const Slic3r::Polyline& bed, BedShapeHint bedhint, bool first_bin_only, std::function progressind, - std::function stopcondition) -{ - using ArrangeResult = _IndexedPackGroup; - - bool ret = true; - - // Get the 2D projected shapes with their 3D model instance pointers - auto shapemap = arr::projectModelFromTop(model); - - // Copy the references for the shapes only as the arranger expects a - // sequence of objects convertible to Item or ClipperPolygon - std::vector> shapes; - shapes.reserve(shapemap.size()); - std::for_each(shapemap.begin(), shapemap.end(), - [&shapes] (ShapeData2D::value_type& it) - { - shapes.push_back(std::ref(it.second)); - }); - - IndexedPackGroup result; - - // If there is no hint about the shape, we will try to guess - if(bedhint.type == BedShapeType::WHO_KNOWS) bedhint = bedShape(bed); - - BoundingBox bbb(bed); - - auto& cfn = stopcondition; - - auto binbb = Box({ - static_cast(bbb.min(0)), - static_cast(bbb.min(1)) - }, - { - static_cast(bbb.max(0)), - static_cast(bbb.max(1)) - }); - - switch(bedhint.type) { - case BedShapeType::BOX: { - - // Create the arranger for the box shaped bed - AutoArranger arrange(binbb, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::CIRCLE: { - - auto c = bedhint.shape.circ; - auto cc = lnCircle(c); - - AutoArranger arrange(cc, min_obj_distance, progressind, cfn); - result = arrange(shapes.begin(), shapes.end()); - break; - } - case BedShapeType::IRREGULAR: - case BedShapeType::WHO_KNOWS: { - - using P = libnest2d::PolygonImpl; - - auto ctour = Slic3rMultiPoint_to_ClipperPath(bed); - P irrbed = sl::create(std::move(ctour)); - - AutoArranger

arrange(irrbed, min_obj_distance, progressind, cfn); - - // Arrange and return the items with their respective indices within the - // input sequence. - result = arrange(shapes.begin(), shapes.end()); - break; - } - }; - - if(result.empty() || stopcondition()) return false; - - if(first_bin_only) { - applyResult(result.front(), 0, shapemap); - } else { - - const auto STRIDE_PADDING = 1.2; - - Coord stride = static_cast(STRIDE_PADDING* - binbb.width()*SCALING_FACTOR); - Coord batch_offset = 0; - - for(auto& group : result) { - applyResult(group, batch_offset, shapemap); - - // Only the first pack group can be placed onto the print bed. The - // other objects which could not fit will be placed next to the - // print bed - batch_offset += stride; - } - } - - for(auto objptr : model.objects) objptr->invalidate_bounding_box(); - - return ret && result.size() == 1; -} + std::function stopcondition); } + } #endif // MODELARRANGE_HPP diff --git a/src/slic3r/AppController.cpp b/src/slic3r/AppController.cpp deleted file mode 100644 index 4cc87bb7e..000000000 --- a/src/slic3r/AppController.cpp +++ /dev/null @@ -1,364 +0,0 @@ -#include "AppController.hpp" - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - - -namespace Slic3r { - -class AppControllerGui::PriData { -public: - std::mutex m; - std::thread::id ui_thread; - - inline explicit PriData(std::thread::id uit): ui_thread(uit) {} -}; - -AppControllerGui::AppControllerGui() - :m_pri_data(new PriData(std::this_thread::get_id())) {} - -AppControllerGui::~AppControllerGui() { - m_pri_data.reset(); -} - -bool AppControllerGui::is_main_thread() const -{ - return m_pri_data->ui_thread == std::this_thread::get_id(); -} - -// namespace GUI { -// PresetBundle* get_preset_bundle(); -// } - -static const PrintObjectStep STEP_SLICE = posSlice; -static const PrintObjectStep STEP_PERIMETERS = posPerimeters; -static const PrintObjectStep STEP_PREPARE_INFILL = posPrepareInfill; -static const PrintObjectStep STEP_INFILL = posInfill; -static const PrintObjectStep STEP_SUPPORTMATERIAL = posSupportMaterial; -static const PrintStep STEP_SKIRT = psSkirt; -static const PrintStep STEP_BRIM = psBrim; -static const PrintStep STEP_WIPE_TOWER = psWipeTower; - -ProgresIndicatorPtr AppControllerGui::global_progress_indicator() { - ProgresIndicatorPtr ret; - - m_pri_data->m.lock(); - ret = m_global_progressind; - m_pri_data->m.unlock(); - - return ret; -} - -void AppControllerGui::global_progress_indicator(ProgresIndicatorPtr gpri) -{ - m_pri_data->m.lock(); - m_global_progressind = gpri; - m_pri_data->m.unlock(); -} - -PrintController::PngExportData -PrintController::query_png_export_data(const DynamicPrintConfig& conf) -{ - PngExportData ret; - - auto c = GUI::get_appctl(); - auto zippath = c->query_destination_path("Output zip file", "*.zip", - "export-png", - "out"); - - ret.zippath = zippath; - - ret.width_mm = conf.opt_float("display_width"); - ret.height_mm = conf.opt_float("display_height"); - - ret.width_px = conf.opt_int("display_pixels_x"); - ret.height_px = conf.opt_int("display_pixels_y"); - - auto opt_corr = conf.opt("printer_correction"); - - if(opt_corr) { - ret.corr_x = opt_corr->values[0]; - ret.corr_y = opt_corr->values[1]; - ret.corr_z = opt_corr->values[2]; - } - - ret.exp_time_first_s = conf.opt_float("initial_exposure_time"); - ret.exp_time_s = conf.opt_float("exposure_time"); - - return ret; -} - -void PrintController::slice(ProgresIndicatorPtr pri) -{ - m_print->set_status_callback([pri](int st, const std::string& msg){ - pri->update(unsigned(st), msg); - }); - - m_print->process(); -} - -void PrintController::slice() -{ - auto ctl = GUI::get_appctl(); - auto pri = ctl->global_progress_indicator(); - if(!pri) pri = ctl->create_progress_indicator(100, L("Slicing")); - slice(pri); -} - -template<> class LayerWriter { - Zipper m_zip; -public: - - inline LayerWriter(const std::string& zipfile_path): m_zip(zipfile_path) {} - - inline void next_entry(const std::string& fname) { m_zip.next_entry(fname); } - - inline std::string get_name() const { return m_zip.get_name(); } - - template inline LayerWriter& operator<<(const T& arg) { - m_zip.stream() << arg; return *this; - } - - inline void close() { m_zip.close(); } -}; - -void PrintController::slice_to_png() -{ -// using Pointf3 = Vec3d; - -// auto ctl = GUI::get_appctl(); -// auto presetbundle = GUI::wxGetApp().preset_bundle; - -// assert(presetbundle); - -// // FIXME: this crashes in command line mode -// auto pt = presetbundle->printers.get_selected_preset().printer_technology(); -// if(pt != ptSLA) { -// ctl->report_issue(IssueType::ERR, L("Printer technology is not SLA!"), -// L("Error")); -// return; -// } - -// auto conf = presetbundle->full_config(); -// conf.validate(); - -// auto exd = query_png_export_data(conf); -// if(exd.zippath.empty()) return; - -// Print *print = m_print; - -// try { -// print->apply_config(conf); -// print->validate(); -// } catch(std::exception& e) { -// ctl->report_issue(IssueType::ERR, e.what(), "Error"); -// return; -// } - -// // TODO: copy the model and work with the copy only -// bool correction = false; -// if(exd.corr_x != 1.0 || exd.corr_y != 1.0 || exd.corr_z != 1.0) { -// correction = true; -//// print->invalidate_all_steps(); - -//// for(auto po : print->objects) { -//// po->model_object()->scale( -//// Pointf3(exd.corr_x, exd.corr_y, exd.corr_z) -//// ); -//// po->model_object()->invalidate_bounding_box(); -//// po->reload_model_instances(); -//// po->invalidate_all_steps(); -//// } -// } - -// // Turn back the correction scaling on the model. -// auto scale_back = [this, print, correction, exd]() { -// if(correction) { // scale the model back -//// print->invalidate_all_steps(); -//// for(auto po : print->objects) { -//// po->model_object()->scale( -//// Pointf3(1.0/exd.corr_x, 1.0/exd.corr_y, 1.0/exd.corr_z) -//// ); -//// po->model_object()->invalidate_bounding_box(); -//// po->reload_model_instances(); -//// po->invalidate_all_steps(); -//// } -// } -// }; - -// auto print_bb = print->bounding_box(); -// Vec2d punsc = unscale(print_bb.size()); - -// // If the print does not fit into the print area we should cry about it. -// if(px(punsc) > exd.width_mm || py(punsc) > exd.height_mm) { -// std::stringstream ss; - -// ss << L("Print will not fit and will be truncated!") << "\n" -// << L("Width needed: ") << px(punsc) << " mm\n" -// << L("Height needed: ") << py(punsc) << " mm\n"; - -// if(!ctl->report_issue(IssueType::WARN_Q, ss.str(), L("Warning"))) { -// scale_back(); -// return; -// } -// } - -// auto pri = ctl->create_progress_indicator( -// 200, L("Slicing to zipped png files...")); - -// pri->on_cancel([&print](){ print->cancel(); }); - -// try { -// pri->update(0, L("Slicing...")); -// slice(pri); -// } catch (std::exception& e) { -// ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred")); -// scale_back(); -// if(print->canceled()) print->restart(); -// return; -// } - -// auto initstate = unsigned(pri->state()); -// print->set_status_callback([pri, initstate](int st, const std::string& msg) -// { -// pri->update(initstate + unsigned(st), msg); -// }); - -// try { -// print_to( *print, exd.zippath, -// exd.width_mm, exd.height_mm, -// exd.width_px, exd.height_px, -// exd.exp_time_s, exd.exp_time_first_s); - -// } catch (std::exception& e) { -// ctl->report_issue(IssueType::ERR, e.what(), L("Exception occurred")); -// } - -// scale_back(); -// if(print->canceled()) print->restart(); -// print->set_status_default(); -} - -const PrintConfig &PrintController::config() const -{ - return m_print->config(); -} - -void ProgressIndicator::message_fmt( - const std::string &fmtstr, ...) { - std::stringstream ss; - va_list args; - va_start(args, fmtstr); - - auto fmt = fmtstr.begin(); - - while (*fmt != '\0') { - if (*fmt == 'd') { - int i = va_arg(args, int); - ss << i << '\n'; - } else if (*fmt == 'c') { - // note automatic conversion to integral type - int c = va_arg(args, int); - ss << static_cast(c) << '\n'; - } else if (*fmt == 'f') { - double d = va_arg(args, double); - ss << d << '\n'; - } - ++fmt; - } - - va_end(args); - message(ss.str()); -} - -void AppController::arrange_model() -{ - using Coord = libnest2d::TCoord; - - auto ctl = GUI::get_appctl(); - - if(m_arranging.load()) return; - - // to prevent UI reentrancies - m_arranging.store(true); - - unsigned count = 0; - for(auto obj : m_model->objects) count += obj->instances.size(); - - auto pind = ctl->global_progress_indicator(); - - float pmax = 1.0; - - if(pind) { - pmax = pind->max(); - - // Set the range of the progress to the object count - pind->max(count); - - pind->on_cancel([this](){ - m_arranging.store(false); - }); - } - - auto dist = print_ctl()->config().min_object_distance(); - - // Create the arranger config - auto min_obj_distance = static_cast(dist/SCALING_FACTOR); - - auto& bedpoints = print_ctl()->config().bed_shape.values; - Polyline bed; bed.points.reserve(bedpoints.size()); - for(auto& v : bedpoints) - bed.append(Point::new_scale(v(0), v(1))); - - if(pind) pind->update(0, L("Arranging objects...")); - - try { - arr::BedShapeHint hint; - // TODO: from Sasha from GUI - hint.type = arr::BedShapeType::WHO_KNOWS; - - arr::arrange(*m_model, - min_obj_distance, - bed, - hint, - false, // create many piles not just one pile - [this, pind, &ctl, count](unsigned rem) { - if(pind) - pind->update(count - rem, L("Arranging objects...")); - - ctl->process_events(); - }, [this] () { return !m_arranging.load(); }); - } catch(std::exception& e) { - std::cerr << e.what() << std::endl; - ctl->report_issue(IssueType::ERR, - L("Could not arrange model objects! " - "Some geometries may be invalid."), - L("Exception occurred")); - } - - // Restore previous max value - if(pind) { - pind->max(pmax); - pind->update(0, m_arranging.load() ? L("Arranging done.") : - L("Arranging canceled.")); - - pind->on_cancel(/*remove cancel function*/); - } - - m_arranging.store(false); -} - -} diff --git a/src/slic3r/AppController.hpp b/src/slic3r/AppController.hpp deleted file mode 100644 index a34e5d035..000000000 --- a/src/slic3r/AppController.hpp +++ /dev/null @@ -1,414 +0,0 @@ -#ifndef APPCONTROLLER_HPP -#define APPCONTROLLER_HPP - -#include -#include -#include -#include -#include - -#include "GUI/ProgressIndicator.hpp" - -#include - -namespace Slic3r { - -class Model; -class Print; -class PrintObject; -class PrintConfig; -class ProgressStatusBar; -class DynamicPrintConfig; - -/// A Progress indicator object smart pointer -using ProgresIndicatorPtr = std::shared_ptr; - -using FilePath = std::string; -using FilePathList = std::vector; - -/// Common runtime issue types -enum class IssueType { - INFO, - WARN, - WARN_Q, // Warning with a question to continue - ERR, - FATAL -}; - -/** - * @brief A boilerplate class for creating application logic. It should provide - * features as issue reporting and progress indication, etc... - * - * The lower lever UI independent classes can be manipulated with a subclass - * of this controller class. We can also catch any exceptions that lower level - * methods could throw and display appropriate errors and warnings. - * - * Note that the outer and the inner interface of this class is free from any - * UI toolkit dependencies. We can implement it with any UI framework or make it - * a cli client. - */ -class AppControllerBase { -public: - - using Ptr = std::shared_ptr; - - inline virtual ~AppControllerBase() {} - - /** - * @brief Query some paths from the user. - * - * It should display a file chooser dialog in case of a UI application. - * @param title Title of a possible query dialog. - * @param extensions Recognized file extensions. - * @return Returns a list of paths chosen by the user. - */ - virtual FilePathList query_destination_paths( - const std::string& title, - const std::string& extensions, - const std::string& functionid = "", - const std::string& hint = "") const = 0; - - /** - * @brief Same as query_destination_paths but works for directories only. - */ - virtual FilePathList query_destination_dirs( - const std::string& title, - const std::string& functionid = "", - const std::string& hint = "") const = 0; - - /** - * @brief Same as query_destination_paths but returns only one path. - */ - virtual FilePath query_destination_path( - const std::string& title, - const std::string& extensions, - const std::string& functionid = "", - const std::string& hint = "") const = 0; - - /** - * @brief Report an issue to the user be it fatal or recoverable. - * - * In a UI app this should display some message dialog. - * - * @param issuetype The type of the runtime issue. - * @param description A somewhat longer description of the issue. - * @param brief A very brief description. Can be used for message dialog - * title. - */ - virtual bool report_issue(IssueType issuetype, - const std::string& description, - const std::string& brief) = 0; - - /** - * @brief Return the global progress indicator for the current controller. - * Can be empty as well. - * - * Only one thread should use the global indicator at a time. - */ - virtual ProgresIndicatorPtr global_progress_indicator() = 0; - - virtual void global_progress_indicator(ProgresIndicatorPtr gpri) = 0; - - /** - * @brief A predicate telling the caller whether it is the thread that - * created the AppConroller object itself. This probably means that the - * execution is in the UI thread. Otherwise it returns false meaning that - * some worker thread called this function. - * @return Return true for the same caller thread that created this - * object and false for every other. - */ - virtual bool is_main_thread() const = 0; - - /** - * @brief The frontend supports asynch execution. - * - * A Graphic UI will support this, a CLI may not. This can be used in - * subclass methods to decide whether to start threads for block free UI. - * - * Note that even a progress indicator's update called regularly can solve - * the blocking UI problem in some cases even when an event loop is present. - * This is how wxWidgets gauge work but creating a separate thread will make - * the UI even more fluent. - * - * @return true if a job or method can be executed asynchronously, false - * otherwise. - */ - virtual bool supports_asynch() const = 0; - - virtual void process_events() = 0; - - /** - * @brief Create a new progress indicator and return a smart pointer to it. - * @param statenum The number of states for the given procedure. - * @param title The title of the procedure. - * @param firstmsg The message for the first subtask to be displayed. - * @return Smart pointer to the created object. - */ - virtual ProgresIndicatorPtr create_progress_indicator( - unsigned statenum, - const std::string& title, - const std::string& firstmsg = "") const = 0; -}; - -/** - * @brief Implementation of AppControllerBase for the GUI app - */ -class AppControllerGui: public AppControllerBase { -private: - class PriData; // Some structure to store progress indication data - - // Pimpl data for thread safe progress indication features - std::unique_ptr m_pri_data; - -public: - - AppControllerGui(); - - virtual ~AppControllerGui(); - - virtual FilePathList query_destination_paths( - const std::string& title, - const std::string& extensions, - const std::string& functionid, - const std::string& hint) const override; - - virtual FilePathList query_destination_dirs( - const std::string& /*title*/, - const std::string& /*functionid*/, - const std::string& /*hint*/) const override { return {}; } - - virtual FilePath query_destination_path( - const std::string& title, - const std::string& extensions, - const std::string& functionid, - const std::string& hint) const override; - - virtual bool report_issue(IssueType issuetype, - const std::string& description, - const std::string& brief = std::string()) override; - - virtual ProgresIndicatorPtr global_progress_indicator() override; - - virtual void global_progress_indicator(ProgresIndicatorPtr gpri) override; - - virtual bool is_main_thread() const override; - - virtual bool supports_asynch() const override; - - virtual void process_events() override; - - virtual ProgresIndicatorPtr create_progress_indicator( - unsigned statenum, - const std::string& title, - const std::string& firstmsg) const override; - -protected: - - // This is a global progress indicator placeholder. In the Slic3r UI it can - // contain the progress indicator on the statusbar. - ProgresIndicatorPtr m_global_progressind; -}; - -class AppControllerCli: public AppControllerBase { - - class CliProgress : public ProgressIndicator { - std::string m_msg, m_title; - public: - virtual void message(const std::string& msg) override { - m_msg = msg; - } - - virtual void title(const std::string& title) override { - m_title = title; - } - }; - -public: - - AppControllerCli() { - std::cout << "Cli AppController ready..." << std::endl; - m_global_progressind = std::make_shared(); - } - - virtual ~AppControllerCli() {} - - virtual FilePathList query_destination_paths( - const std::string& /*title*/, - const std::string& /*extensions*/, - const std::string& /*functionid*/, - const std::string& /*hint*/) const override { return {}; } - - virtual FilePathList query_destination_dirs( - const std::string& /*title*/, - const std::string& /*functionid*/, - const std::string& /*hint*/) const override { return {}; } - - virtual FilePath query_destination_path( - const std::string& /*title*/, - const std::string& /*extensions*/, - const std::string& /*functionid*/, - const std::string& /*hint*/) const override { return "out.zip"; } - - virtual bool report_issue(IssueType /*issuetype*/, - const std::string& description, - const std::string& brief) override { - std::cerr << brief << ": " << description << std::endl; - return true; - } - - virtual ProgresIndicatorPtr global_progress_indicator() override { - return m_global_progressind; - } - - virtual void global_progress_indicator(ProgresIndicatorPtr) override {} - - virtual bool is_main_thread() const override { return true; } - - virtual bool supports_asynch() const override { return false; } - - virtual void process_events() override {} - - virtual ProgresIndicatorPtr create_progress_indicator( - unsigned /*statenum*/, - const std::string& /*title*/, - const std::string& /*firstmsg*/) const override { - return std::make_shared(); - } - -protected: - - // This is a global progress indicator placeholder. In the Slic3r UI it can - // contain the progress indicator on the statusbar. - ProgresIndicatorPtr m_global_progressind; -}; - -class Zipper { - struct Impl; - std::unique_ptr m_impl; -public: - - Zipper(const std::string& zipfilepath); - ~Zipper(); - - void next_entry(const std::string& fname); - - std::string get_name() const; - - std::ostream& stream(); - - void close(); -}; - -/** - * @brief Implementation of the printing logic. - */ -class PrintController { - Print *m_print = nullptr; - std::function m_rempools; -protected: - - // Data structure with the png export input data - struct PngExportData { - std::string zippath; // output zip file - unsigned long width_px = 1440; // resolution - rows - unsigned long height_px = 2560; // resolution columns - double width_mm = 68.0, height_mm = 120.0; // dimensions in mm - double exp_time_first_s = 35.0; // first exposure time - double exp_time_s = 8.0; // global exposure time - double corr_x = 1.0; // offsetting in x - double corr_y = 1.0; // offsetting in y - double corr_z = 1.0; // offsetting in y - }; - - // Should display a dialog with the input fields for printing to png - PngExportData query_png_export_data(const DynamicPrintConfig&); - - // The previous export data, to pre-populate the dialog - PngExportData m_prev_expdata; - - void slice(ProgresIndicatorPtr pri); - -public: - - // Must be public for perl to use it - explicit inline PrintController(Print *print): m_print(print) {} - - PrintController(const PrintController&) = delete; - PrintController(PrintController&&) = delete; - - using Ptr = std::unique_ptr; - - inline static Ptr create(Print *print) { - return PrintController::Ptr( new PrintController(print) ); - } - - /** - * @brief Slice the loaded print scene. - */ - void slice(); - - /** - * @brief Slice the print into zipped png files. - */ - void slice_to_png(); - - const PrintConfig& config() const; -}; - -/** - * @brief Top level controller. - */ -class AppController { - Model *m_model = nullptr; - PrintController::Ptr printctl; - std::atomic m_arranging; -public: - - /** - * @brief Get the print controller object. - * - * @return Return a raw pointer instead of a smart one for perl to be able - * to use this function and access the print controller. - */ - PrintController * print_ctl() { return printctl.get(); } - - /** - * @brief Set a model object. - * - * @param model A raw pointer to the model object. This can be used from - * perl. - */ - void set_model(Model *model) { m_model = model; } - - /** - * @brief Set the print object from perl. - * - * This will create a print controller that will then be accessible from - * perl. - * @param print A print object which can be a perl-ish extension as well. - */ - void set_print(Print *print) { - printctl = PrintController::create(print); - } - - /** - * @brief Set up a global progress indicator. - * - * In perl we have a progress indicating status bar on the bottom of the - * window which is defined and created in perl. We can pass the ID-s of the - * gauge and the statusbar id and make a wrapper implementation of the - * ProgressIndicator interface so we can use this GUI widget from C++. - * - * This function should be called from perl. - * - * @param gauge_id The ID of the gague widget of the status bar. - * @param statusbar_id The ID of the status bar. - */ - void set_global_progress_indicator(ProgressStatusBar *prs); - - void arrange_model(); -}; - -} - -#endif // APPCONTROLLER_HPP diff --git a/src/slic3r/AppControllerWx.cpp b/src/slic3r/AppControllerWx.cpp deleted file mode 100644 index 25cd739f3..000000000 --- a/src/slic3r/AppControllerWx.cpp +++ /dev/null @@ -1,340 +0,0 @@ -#include "AppController.hpp" - -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -// This source file implements the UI dependent methods of the AppControllers. -// It will be clear what is needed to be reimplemented in case of a UI framework -// change or a CLI client creation. In this particular case we use wxWidgets to -// implement everything. - -namespace Slic3r { - -bool AppControllerGui::supports_asynch() const -{ - return true; -} - -void AppControllerGui::process_events() -{ - wxYieldIfNeeded(); -} - -FilePathList AppControllerGui::query_destination_paths( - const std::string &title, - const std::string &extensions, - const std::string &/*functionid*/, - const std::string& hint) const -{ - - wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); - dlg.SetWildcard(extensions); - - dlg.SetFilename(hint); - - FilePathList ret; - - if(dlg.ShowModal() == wxID_OK) { - wxArrayString paths; - dlg.GetPaths(paths); - for(auto& p : paths) ret.push_back(p.ToStdString()); - } - - return ret; -} - -FilePath AppControllerGui::query_destination_path( - const std::string &title, - const std::string &extensions, - const std::string &/*functionid*/, - const std::string& hint) const -{ - wxFileDialog dlg(wxTheApp->GetTopWindow(), _(title) ); - dlg.SetWildcard(extensions); - - dlg.SetFilename(hint); - - FilePath ret; - - if(dlg.ShowModal() == wxID_OK) { - ret = FilePath(dlg.GetPath()); - } - - return ret; -} - -bool AppControllerGui::report_issue(IssueType issuetype, - const std::string &description, - const std::string &brief) -{ - auto icon = wxICON_INFORMATION; - auto style = wxOK|wxCENTRE; - switch(issuetype) { - case IssueType::INFO: break; - case IssueType::WARN: icon = wxICON_WARNING; break; - case IssueType::WARN_Q: icon = wxICON_WARNING; style |= wxCANCEL; break; - case IssueType::ERR: - case IssueType::FATAL: icon = wxICON_ERROR; - } - - auto ret = wxMessageBox(_(description), _(brief), icon | style); - return ret != wxCANCEL; -} - -wxDEFINE_EVENT(PROGRESS_STATUS_UPDATE_EVENT, wxCommandEvent); - -struct Zipper::Impl { - wxFileName fpath; - wxFFileOutputStream zipfile; - wxZipOutputStream zipstream; - wxStdOutputStream pngstream; - - Impl(const std::string& zipfile_path): - fpath(zipfile_path), - zipfile(zipfile_path), - zipstream(zipfile), - pngstream(zipstream) - { - if(!zipfile.IsOk()) - throw std::runtime_error(L("Cannot create zip file.")); - } -}; - -Zipper::Zipper(const std::string &zipfilepath) -{ - m_impl.reset(new Impl(zipfilepath)); -} - -Zipper::~Zipper() {} - -void Zipper::next_entry(const std::string &fname) -{ - m_impl->zipstream.PutNextEntry(fname); -} - -std::string Zipper::get_name() const -{ - return m_impl->fpath.GetName().ToStdString(); -} - -std::ostream &Zipper::stream() -{ - return m_impl->pngstream; -} - -void Zipper::close() -{ - m_impl->zipstream.Close(); - m_impl->zipfile.Close(); -} - -namespace { - -/* - * A simple thread safe progress dialog implementation that can be used from - * the main thread as well. - */ -class GuiProgressIndicator: - public ProgressIndicator, public wxEvtHandler { - - wxProgressDialog m_gauge; - using Base = ProgressIndicator; - wxString m_message; - int m_range; wxString m_title; - bool m_is_asynch = false; - - const int m_id = wxWindow::NewControlId(); - - // status update handler - void _state( wxCommandEvent& evt) { - unsigned st = evt.GetInt(); - m_message = evt.GetString(); - _state(st); - } - - // Status update implementation - void _state( unsigned st) { - if(!m_gauge.IsShown()) m_gauge.ShowModal(); - Base::state(st); - if(!m_gauge.Update(static_cast(st), m_message)) { - cancel(); - } - } - -public: - - /// Setting whether it will be used from the UI thread or some worker thread - inline void asynch(bool is) { m_is_asynch = is; } - - /// Get the mode of parallel operation. - inline bool asynch() const { return m_is_asynch; } - - inline GuiProgressIndicator(int range, const wxString& title, - const wxString& firstmsg) : - m_gauge(title, firstmsg, range, wxTheApp->GetTopWindow(), - wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_CAN_ABORT), - - m_message(firstmsg), - m_range(range), m_title(title) - { - Base::max(static_cast(range)); - Base::states(static_cast(range)); - - Bind(PROGRESS_STATUS_UPDATE_EVENT, - &GuiProgressIndicator::_state, - this, m_id); - } - - virtual void state(float val) override { - state(static_cast(val)); - } - - void state(unsigned st) { - // send status update event - if(m_is_asynch) { - auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, m_id); - evt->SetInt(st); - evt->SetString(m_message); - wxQueueEvent(this, evt); - } else _state(st); - } - - virtual void message(const std::string & msg) override { - m_message = _(msg); - } - - virtual void messageFmt(const std::string& fmt, ...) { - va_list arglist; - va_start(arglist, fmt); - m_message = wxString::Format(_(fmt), arglist); - va_end(arglist); - } - - virtual void title(const std::string & title) override { - m_title = _(title); - } -}; -} - -ProgresIndicatorPtr AppControllerGui::create_progress_indicator( - unsigned statenum, - const std::string& title, - const std::string& firstmsg) const -{ - auto pri = - std::make_shared(statenum, title, firstmsg); - - // We set up the mode of operation depending of the creator thread's - // identity - pri->asynch(!is_main_thread()); - - return pri; -} - -namespace { - -class Wrapper: public ProgressIndicator, public wxEvtHandler { - ProgressStatusBar *m_sbar; - using Base = ProgressIndicator; - wxString m_message; - AppControllerBase& m_ctl; - - void showProgress(bool show = true) { - m_sbar->show_progress(show); - } - - void _state(unsigned st) { - if( st <= ProgressIndicator::max() ) { - Base::state(st); - m_sbar->set_status_text(m_message); - m_sbar->set_progress(st); - } - } - - // status update handler - void _state( wxCommandEvent& evt) { - unsigned st = evt.GetInt(); _state(st); - } - - const int id_ = wxWindow::NewControlId(); - -public: - - inline Wrapper(ProgressStatusBar *sbar, - AppControllerBase& ctl): - m_sbar(sbar), m_ctl(ctl) - { - Base::max(static_cast(m_sbar->get_range())); - Base::states(static_cast(m_sbar->get_range())); - - Bind(PROGRESS_STATUS_UPDATE_EVENT, - &Wrapper::_state, - this, id_); - } - - virtual void state(float val) override { - state(unsigned(val)); - } - - virtual void max(float val) override { - if(val > 1.0) { - m_sbar->set_range(static_cast(val)); - ProgressIndicator::max(val); - } - } - - void state(unsigned st) { - if(!m_ctl.is_main_thread()) { - auto evt = new wxCommandEvent(PROGRESS_STATUS_UPDATE_EVENT, id_); - evt->SetInt(st); - wxQueueEvent(this, evt); - } else { - _state(st); - } - } - - virtual void message(const std::string & msg) override { - m_message = _(msg); - } - - virtual void message_fmt(const std::string& fmt, ...) override { - va_list arglist; - va_start(arglist, fmt); - m_message = wxString::Format(_(fmt), arglist); - va_end(arglist); - } - - virtual void title(const std::string & /*title*/) override {} - - virtual void on_cancel(CancelFn fn) override { - m_sbar->set_cancel_callback(fn); - Base::on_cancel(fn); - } - -}; -} - -void AppController::set_global_progress_indicator(ProgressStatusBar *prsb) -{ - if(prsb) { - auto ctl = GUI::get_appctl(); - ctl->global_progress_indicator(std::make_shared(prsb, *ctl)); - } -} - -} diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4ecccb041..f4bea7d36 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -123,9 +123,6 @@ add_library(libslic3r_gui STATIC Utils/Time.hpp Utils/HexFile.cpp Utils/HexFile.hpp - AppController.hpp - AppController.cpp - AppControllerWx.cpp ) target_link_libraries(libslic3r_gui libslic3r avrdude) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index b6136a15c..5828e9a22 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -1,6 +1,5 @@ #include "GUI.hpp" #include "GUI_App.hpp" -#include "../AppController.hpp" #include "WipeTowerDialog.hpp" #include @@ -453,23 +452,4 @@ void desktop_open_datadir_folder() #endif } -namespace { -AppControllerPtr g_appctl; -} - -AppControllerPtr get_appctl() -{ - return g_appctl; -} - -void set_cli_appctl() -{ - g_appctl = std::make_shared(); -} - -void set_gui_appctl() -{ - g_appctl = std::make_shared(); -} - } } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a378a067e..fe3cd48ed 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -11,7 +11,6 @@ #include "Tab.hpp" #include "PresetBundle.hpp" -#include "../AppController.hpp" #include "ProgressStatusBar.hpp" #include "3DScene.hpp" #include "Print.hpp" @@ -30,8 +29,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL m_no_plater(no_plater), m_loaded(loaded) { - m_appController = new Slic3r::AppController(); - // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -59,14 +56,6 @@ wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAUL SLIC3R_VERSION + _(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"))); - m_appController->set_model(&m_plater->model()); - m_appController->set_print(&m_plater->print()); - - GUI::set_gui_appctl(); - - // Make the global status bar and its progress indicator available in C++ - m_appController->set_global_progress_indicator(m_statusbar); - m_loaded = true; // initialize layout @@ -373,7 +362,7 @@ void MainFrame::slice_to_png() { // m_plater->stop_background_process(); // m_plater->async_apply_config(); - m_appController->print_ctl()->slice_to_png(); +// m_appController->print_ctl()->slice_to_png(); } // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 04201b709..eb45e155e 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -20,7 +20,6 @@ class wxProgressDialog; namespace Slic3r { class ProgressStatusBar; -class AppController; // #define _(s) Slic3r::GUI::I18N::translate((s)) @@ -54,7 +53,6 @@ class MainFrame : public wxFrame wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; - AppController* m_appController { nullptr }; std::map m_options_tabs; wxMenuItem* m_menu_item_reslice_now { nullptr }; @@ -97,8 +95,6 @@ public: void select_tab(size_t tab) const; void select_view(const std::string& direction); - AppController* app_controller() { return m_appController; } - std::vector& get_preset_tabs(); Plater* m_plater { nullptr }; @@ -110,4 +106,4 @@ public: } // GUI } //Slic3r -#endif // slic3r_MainFrame_hpp_ \ No newline at end of file +#endif // slic3r_MainFrame_hpp_ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index edf59b27a..6b29e72f3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -25,6 +25,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/GCode/PreviewData.hpp" @@ -33,7 +34,7 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/3mf.hpp" -#include "slic3r/AppController.hpp" +//#include "slic3r/AppController.hpp" #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -887,6 +888,7 @@ struct Plater::priv wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can Preview *preview; BackgroundSlicingProcess background_process; + std::atomic arranging; wxTimer background_process_timer; @@ -1470,13 +1472,86 @@ void Plater::priv::mirror(Axis axis) void Plater::priv::arrange() { - this->background_process.stop(); - main_frame->app_controller()->arrange_model(); + // don't do anything if currently arranging. Then this is a re-entrance + if(arranging.load()) return; + + // Guard the arrange process + arranging.store(true); + + _3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange()); + + this->background_process.stop(); + unsigned count = 0; + for(auto obj : model.objects) count += obj->instances.size(); + + auto prev_range = statusbar()->get_range(); + statusbar()->set_range(count); + + auto statusfn = [this, count] (unsigned st, const std::string& msg) { + /* // In case we would run the arrange asynchronously + wxCommandEvent event(EVT_PROGRESS_BAR); + event.SetInt(st); + event.SetString(msg); + wxQueueEvent(this->q, event.Clone()); */ + statusbar()->set_progress(count - st); + statusbar()->set_status_text(msg); + + // ok, this is dangerous, but we are protected by the atomic flag + // 'arranging'. This call is needed for the cancel button to work. + wxYieldIfNeeded(); + }; + + statusbar()->set_cancel_callback([this, statusfn](){ + arranging.store(false); + statusfn(0, L("Arranging canceled")); + }); + + static const std::string arrangestr = L("Arranging"); + + // FIXME: I don't know how to obtain the minimum distance, it depends + // on printer technology. I guess the following should work but it crashes. + double dist = 6; //PrintConfig::min_object_distance(config); + + auto min_obj_distance = static_cast(dist/SCALING_FACTOR); + + const auto *bed_shape_opt = config->opt("bed_shape"); + + assert(bed_shape_opt); + auto& bedpoints = bed_shape_opt->values; + Polyline bed; bed.points.reserve(bedpoints.size()); + for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1))); + + statusfn(0, arrangestr); + + try { + arr::BedShapeHint hint; + + // TODO: from Sasha from GUI or + hint.type = arr::BedShapeType::WHO_KNOWS; + + arr::arrange(model, + min_obj_distance, + bed, + hint, + false, // create many piles not just one pile + [statusfn](unsigned st) { statusfn(st, arrangestr); }, + [this] () { return !arranging.load(); }); + } catch(std::exception& /*e*/) { + GUI::show_error(this->q, L("Could not arrange model objects! " + "Some geometries may be invalid.")); + } + + statusfn(0, L("Arranging done.")); + statusbar()->set_range(prev_range); + statusbar()->set_cancel_callback(); // remove cancel button + arranging.store(false); + this->schedule_background_process(); - // ignore arrange failures on purpose: user has visual feedback and we don't need to warn him - // when parts don't fit in print bed + // ignore arrange failures on purpose: user has visual feedback and we + // don't need to warn him when parts don't fit in print bed + _3DScene::enable_toolbar_item(canvas3D, "arrange", can_arrange()); update(); } @@ -1932,7 +2007,7 @@ bool Plater::priv::can_delete_all() const bool Plater::priv::can_arrange() const { - return !model.objects.empty(); + return !model.objects.empty() && !arranging.load(); } bool Plater::priv::can_mirror() const diff --git a/xs/xsp/AppController.xsp b/xs/xsp/AppController.xsp deleted file mode 100644 index 8156b0ad2..000000000 --- a/xs/xsp/AppController.xsp +++ /dev/null @@ -1,29 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "slic3r/AppController.hpp" -#include "libslic3r/Model.hpp" -#include "libslic3r/Print.hpp" -#include "slic3r/GUI/ProgressStatusBar.hpp" -%} - -%name{Slic3r::PrintController} class PrintController { - - PrintController(Print *print); - - void slice_to_png(); - void slice(); -}; - -%name{Slic3r::AppController} class AppController { - - AppController(); - - PrintController *print_ctl(); - void set_model(Model *model); - void set_print(Print *print); - void set_global_progress_indicator(ProgressStatusBar *prs); - - void arrange_model(); -}; \ No newline at end of file diff --git a/xs/xsp/my.map b/xs/xsp/my.map index a91c40726..90a5feaf7 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -216,8 +216,6 @@ Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T AppConfig* O_OBJECT_SLIC3R -AppController* O_OBJECT_SLIC3R -PrintController* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T BackgroundSlicingProcess* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T From 70fdb48c12aa9c3207c1162747f5176b129da642 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 12 Nov 2018 15:35:58 +0100 Subject: [PATCH 4/6] Manipulation with colorprint ticks now calls Plater::schedule_background_process() --- src/slic3r/GUI/GUI_Preview.cpp | 11 ++++++++--- src/slic3r/GUI/GUI_Preview.hpp | 5 ++++- src/slic3r/GUI/Plater.cpp | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 78f8b7462..b7f85d1ad 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -24,7 +24,7 @@ namespace Slic3r { namespace GUI { -Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data) +Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) : m_canvas(nullptr) , m_double_slider_sizer(nullptr) , m_label_view_type(nullptr) @@ -43,6 +43,7 @@ Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, , m_loaded(false) , m_enabled(false) , m_force_sliders_full_range(false) + , m_schedule_background_process(schedule_background_process_func) { if (init(notebook, config, print, gcode_preview_data)) { @@ -488,6 +489,7 @@ void Preview::create_double_slider() Bind(wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) { auto& config = wxGetApp().preset_bundle->project_config; ((config.option("colorprint_heights"))->values) = (m_slider->GetTicksValues()); + m_schedule_background_process(); }); } @@ -529,13 +531,16 @@ void Preview::fill_slider_values(std::vector> &values, } // All ticks that would end up outside the slider range should be erased. - // TODO: this should probably be placed into more appropriate part of code, - // this way it relies on the Preview tab being active. + // TODO: this should be placed into more appropriate part of code, + // this function is e.g. not called when the last object is deleted auto& config = wxGetApp().preset_bundle->project_config; std::vector &ticks_from_config = (config.option("colorprint_heights"))->values; + unsigned int old_size = ticks_from_config.size(); ticks_from_config.erase(std::remove_if(ticks_from_config.begin(), ticks_from_config.end(), [values](double val) { return values.back().second < val; }), ticks_from_config.end()); + if (ticks_from_config.size() != old_size) + m_schedule_background_process(); } void Preview::set_double_slider_thumbs(const bool force_sliders_full_range, diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index ab7544ed8..bafcba1ba 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -40,6 +40,9 @@ class Preview : public wxPanel Print* m_print; GCodePreviewData* m_gcode_preview_data; + // Calling this function object forces Plater::schedule_background_process. + std::function m_schedule_background_process; + unsigned int m_number_extruders; std::string m_preferred_color_mode; @@ -50,7 +53,7 @@ class Preview : public wxPanel PrusaDoubleSlider* m_slider {nullptr}; public: - Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data); + Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); virtual ~Preview(); wxGLCanvas* get_wxglcanvas() { return m_canvas; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6b29e72f3..5a742c07b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1002,7 +1002,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : _3DScene::add_canvas(canvas3D); _3DScene::allow_multisample(canvas3D, GLCanvas3DManager::can_multisample()); notebook->AddPage(canvas3D, _(L("3D"))); - preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data); + preview = new GUI::Preview(notebook, config, &print, &gcode_preview_data, [this](){ schedule_background_process(); }); // XXX: If have OpenGL _3DScene::enable_picking(canvas3D, true); From df658713bf0a1bda2aa05b3612168c3349b1cd29 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 12 Nov 2018 15:36:40 +0100 Subject: [PATCH 5/6] Wipe tower preview not shown in SLA mode --- src/slic3r/GUI/GLCanvas3D.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9293adf5a..707359f3e 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3658,7 +3658,6 @@ void GLCanvas3D::reload_scene(bool force) { if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr)) return; - #if !ENABLE_USE_UNIQUE_GLCONTEXT // ensures this canvas is current if (!set_current()) @@ -3695,7 +3694,7 @@ void GLCanvas3D::reload_scene(bool force) if (m_regenerate_volumes) { - if (m_config->has("nozzle_diameter")) + if (m_config->has("nozzle_diameter") && wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { // Should the wipe tower be visualized ? unsigned int extruders_count = (unsigned int)dynamic_cast(m_config->option("nozzle_diameter"))->values.size(); @@ -3717,7 +3716,6 @@ void GLCanvas3D::reload_scene(bool force) float depth = m_print->get_wipe_tower_depth(); if (!m_print->is_step_done(psWipeTower)) depth = (900.f/w) * (float)(extruders_count - 1) ; - m_volumes.load_wipe_tower_preview(1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); } @@ -6207,16 +6205,18 @@ void GLCanvas3D::_load_shells() ++object_id; } - // adds wipe tower's volume - double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2); - const PrintConfig& config = m_print->config(); - unsigned int extruders_count = config.nozzle_diameter.size(); - if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { - float depth = m_print->get_wipe_tower_depth(); - if (!m_print->is_step_done(psWipeTower)) - depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; - m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, - m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF) { + // adds wipe tower's volume + double max_z = m_print->objects()[0]->model_object()->get_model()->bounding_box().max(2); + const PrintConfig& config = m_print->config(); + unsigned int extruders_count = config.nozzle_diameter.size(); + if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { + float depth = m_print->get_wipe_tower_depth(); + if (!m_print->is_step_done(psWipeTower)) + depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; + m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + m_use_VBOs && m_initialized, !m_print->is_step_done(psWipeTower), m_print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + } } } From d20bac70391cd9edc3d3ba84489fc5fe5e4a2817 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 12 Nov 2018 16:03:29 +0100 Subject: [PATCH 6/6] Added a modifier selection in the object list + set box-subobject's center to the objects center + fixed bug in PrusaObjectDataViewModel.Delete(), when deleting last volume_idx --- src/slic3r/GUI/GUI_ObjectList.cpp | 10 +++++++--- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 4 +++- src/slic3r/GUI/wxExtensions.cpp | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 106ea2d16..a088e13e5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -704,7 +704,7 @@ void ObjectList::load_subobject(int type) parts_changed(obj_idx); for (int i = 0; i < part_names.size(); ++i) { - const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), /**m_bmp_vector[*/type/*]*/); + const wxDataViewItem sel_item = m_objects_model->AddVolumeChild(item, part_names.Item(i), type); if (i == part_names.size() - 1) select_item(sel_item); @@ -786,8 +786,11 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const int const auto& sz = BoundingBoxf(bed_shape).size(); const auto side = 0.1 * std::max(sz(0), sz(1)); - if (type_name == _("Box")) + if (type_name == _("Box")) { mesh = make_cube(side, side, side); + // box sets the base coordinate at 0, 0, move to center of plate + mesh.translate(-side * 0.5, -side * 0.5, 0); + } else if (type_name == _("Cylinder")) mesh = make_cylinder(0.5*side, side); else if (type_name == _("Sphere")) @@ -1251,7 +1254,8 @@ void ObjectList::update_selections() { sels.Add(m_objects_model->GetItemById(selection.get_object_idx())); } - else if (selection.is_single_volume() || selection.is_multiple_volume() || selection.is_multiple_full_object()) { + else if (selection.is_single_volume() || selection.is_modifier() || + selection.is_multiple_volume() || selection.is_multiple_full_object()) { for (auto idx : selection.get_volume_idxs()) { const auto gl_vol = selection.get_volume(idx); if (selection.is_multiple_full_object()) diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 870ed2226..ee35af642 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -86,6 +86,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent): if (option_name == "Rotation") def.min = -360; + else + def.min == -1000; const std::string lower_name = boost::algorithm::to_lower_copy(option_name); @@ -164,7 +166,7 @@ int ObjectManipulation::ol_selection() void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection) { #if ENABLE_MODELVOLUME_TRANSFORM - if (selection.is_single_full_instance()) + if (selection.is_single_full_instance() || selection.is_single_full_object()) #else if (selection.is_single_full_object()) { diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 6d68d4f02..5d0b44d47 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -641,6 +641,7 @@ wxDataViewItem PrusaObjectDataViewModel::Delete(const wxDataViewItem &item) PrusaObjectDataViewModelNode *last_child_node = node_parent->GetNthChild(vol_idx); DeleteSettings(wxDataViewItem(last_child_node)); node_parent->GetChildren().Remove(last_child_node); + node_parent->m_volumes_cnt = 0; delete last_child_node; #ifndef __WXGTK__