From aed6eabae8ebf10995910c93f5ff12cdd46a7580 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Dec 2018 09:52:22 +0100 Subject: [PATCH 01/61] Fixed missing includes --- src/libslic3r/GCode/PostProcessor.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index ba2fa74d9..e44faa220 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -1,5 +1,7 @@ #include "PostProcessor.hpp" +#include + #ifdef WIN32 namespace Slic3r { @@ -25,9 +27,10 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config { if (config.post_process.values.empty()) return; - //config.setenv_(); + + config.setenv_(); auto gcode_file = boost::filesystem::path(path); - if (!boost::filesystem::exists(gcode_file)) + if (! boost::filesystem::exists(gcode_file)) throw std::runtime_error(std::string("Post-processor can't find exported gcode file")); for (std::string script: config.post_process.values) { From 31387af176f7192c7d4f5e81719e76df5f526d00 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Dec 2018 10:40:57 +0100 Subject: [PATCH 02/61] Fixed camera zoom after object scale --- src/slic3r/GUI/GLCanvas3D.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index f0a29faa4..7bf646a3c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4994,6 +4994,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); #if ENABLE_CONSTRAINED_CAMERA_TARGET m_camera.set_scene_box(scene_bounding_box(), *this); + set_camera_zoom(0.0f); #endif // ENABLE_CONSTRAINED_CAMERA_TARGET } From abbc451f805db7e8bb75362bbc47eb6ce8943a56 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Dec 2018 10:38:32 +0100 Subject: [PATCH 03/61] Fixed wrong importing of the 3mf with modifier/part which is contain settings --- src/slic3r/GUI/GUI_ObjectList.cpp | 39 ++++++++++++++++++++++++------- src/slic3r/GUI/MainFrame.cpp | 1 + 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7f6ec04dd..3a7727cfe 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1152,13 +1152,19 @@ void ObjectList::add_object_to_list(size_t obj_idx) // add volumes to the object if (model_object->volumes.size() > 1) { - for (auto id = 0; id < model_object->volumes.size(); id++) - m_objects_model->AddVolumeChild(item, - model_object->volumes[id]->name, - ModelVolume::MODEL_PART, - !model_object->volumes[id]->config.has("extruder") ? 0 : + for (auto id = 0; id < model_object->volumes.size(); id++) { + auto vol_item = m_objects_model->AddVolumeChild(item, + model_object->volumes[id]->name, + model_object->volumes[id]->type()/*ModelVolume::MODEL_PART*/, + !model_object->volumes[id]->config.has("extruder") ? 0 : model_object->volumes[id]->config.option("extruder")->value, - false); + false); + auto opt_keys = model_object->volumes[id]->config.keys(); + if (!opt_keys.empty() && !(opt_keys.size() == 1 && opt_keys[0] == "extruder")) { + select_item(m_objects_model->AddSettingsChild(vol_item)); + Collapse(vol_item); + } + } Expand(item); } @@ -1532,7 +1538,24 @@ void ObjectList::change_part_type() ModelVolume* volume = get_selected_model_volume(); if (!volume) return; + const auto type = volume->type(); + if (type == ModelVolume::MODEL_PART) + { + const int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) return; + + int model_part_cnt = 0; + for (auto vol : (*m_objects)[obj_idx]->volumes) { + if (vol->type() == ModelVolume::MODEL_PART) + ++model_part_cnt; + } + + if (model_part_cnt == 1) { + Slic3r::GUI::show_error(nullptr, _(L("You can't change a type of the last solid part of the object."))); + return; + } + } const wxString names[] = { "Part", "Modifier", "Support Enforcer", "Support Blocker" }; @@ -1552,11 +1575,11 @@ void ObjectList::change_part_type() //(we show additional settings for Part and Modifier and hide it for Support Blocker/Enforcer) const auto settings_item = m_objects_model->GetSettingsItem(item); if (settings_item && - new_type == ModelVolume::SUPPORT_ENFORCER || new_type == ModelVolume::SUPPORT_BLOCKER) { + (new_type == ModelVolume::SUPPORT_ENFORCER || new_type == ModelVolume::SUPPORT_BLOCKER)) { m_objects_model->Delete(settings_item); } else if (!settings_item && - new_type == ModelVolume::MODEL_PART || new_type == ModelVolume::PARAMETER_MODIFIER) { + (new_type == ModelVolume::MODEL_PART || new_type == ModelVolume::PARAMETER_MODIFIER)) { select_item(m_objects_model->AddSettingsChild(item)); } } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index ea96466db..b3dc8ba75 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -795,6 +795,7 @@ void MainFrame::update_ui_from_settings() bool bp_on = wxGetApp().app_config->get("background_processing") == "1"; m_menu_item_reslice_now->Enable(bp_on); m_plater->sidebar().show_button(baReslice, !bp_on); + m_plater->sidebar().Layout(); if (m_plater) m_plater->update_ui_from_settings(); for (auto tab: wxGetApp().tabs_list) From 6888fefc2ef8f3346659a7f319b15b8303ba00eb Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 10 Dec 2018 10:44:30 +0100 Subject: [PATCH 04/61] Fixed constness of Config::env_ --- src/libslic3r/Config.cpp | 2 +- src/libslic3r/Config.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index e3f24d229..88e0c7664 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -336,7 +336,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati return static_cast(raw_opt)->get_abs_value(ratio_over); } -void ConfigBase::setenv_() +void ConfigBase::setenv_() const { t_config_option_keys opt_keys = this->keys(); for (t_config_option_keys::const_iterator it = opt_keys.begin(); it != opt_keys.end(); ++it) { diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index b5a44e176..7f826109a 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -1113,7 +1113,7 @@ public: double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; - void setenv_(); + void setenv_() const; void load(const std::string &file); void load_from_ini(const std::string &file); void load_from_gcode_file(const std::string &file); From 9e952c91224243967b125758fe541d9efba835f1 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Dec 2018 12:59:49 +0100 Subject: [PATCH 05/61] Fixed rendering of caps in sla preview --- src/slic3r/GUI/GLCanvas3D.cpp | 270 ++++++++++++++++++---------------- src/slic3r/GUI/GLCanvas3D.hpp | 8 +- 2 files changed, 154 insertions(+), 124 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 7bf646a3c..9fff0945f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -6084,166 +6084,190 @@ void GLCanvas3D::_render_sla_slices() const return; const SLAPrint* print = this->sla_print(); - if (print->objects().empty()) + const PrintObjects& print_objects = print->objects(); + if (print_objects.empty()) // nothing to render, return return; double clip_min_z = -m_clipping_planes[0].get_data()[3]; double clip_max_z = m_clipping_planes[1].get_data()[3]; - for (const SLAPrintObject* obj : print->objects()) + for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) { - if (obj->is_step_done(slaposIndexSlices)) + const SLAPrintObject* obj = print_objects[i]; + + Pointf3s bottom_obj_triangles; + Pointf3s bottom_sup_triangles; + Pointf3s top_obj_triangles; + Pointf3s top_sup_triangles; + + double shift_z = obj->get_current_elevation(); + double min_z = clip_min_z - shift_z; + double max_z = clip_max_z - shift_z; + + if (m_sla_caps[0].matches(min_z)) + { + SlaCap::ObjectIdToTrianglesMap::const_iterator it = m_sla_caps[0].triangles.find(i); + if (it != m_sla_caps[0].triangles.end()) + { + bottom_obj_triangles = it->second.object; + bottom_sup_triangles = it->second.suppports; + } + } + + if (m_sla_caps[1].matches(max_z)) + { + SlaCap::ObjectIdToTrianglesMap::const_iterator it = m_sla_caps[1].triangles.find(i); + if (it != m_sla_caps[1].triangles.end()) + { + top_obj_triangles = it->second.object; + top_sup_triangles = it->second.suppports; + } + } + + const std::vector& instances = obj->instances(); + struct InstanceTransform + { + Vec3d offset; + float rotation; + }; + + std::vector instance_transforms; + for (const SLAPrintObject::Instance& inst : instances) + { + instance_transforms.push_back({ to_3d(unscale(inst.shift), shift_z), Geometry::rad2deg(inst.rotation) }); + } + + if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && obj->is_step_done(slaposIndexSlices)) { const std::vector& model_slices = obj->get_model_slices(); const std::vector& support_slices = obj->get_support_slices(); - const std::vector& instances = obj->instances(); - double shift_z = obj->get_current_elevation(); - struct InstanceTransform + const SLAPrintObject::SliceIndex& index = obj->get_slice_index(); + SLAPrintObject::SliceIndex::const_iterator it_min_z = std::find_if(index.begin(), index.end(), [min_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(min_z - id.first) < EPSILON; }); + SLAPrintObject::SliceIndex::const_iterator it_max_z = std::find_if(index.begin(), index.end(), [max_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(max_z - id.first) < EPSILON; }); + + if (it_min_z != index.end()) { - Vec3d offset; - float rotation; - }; - - std::vector instance_transforms; - for (const SLAPrintObject::Instance& inst : instances) - { - instance_transforms.push_back({ to_3d(unscale(inst.shift), shift_z), Geometry::rad2deg(inst.rotation) }); - } - - double min_z = clip_min_z - shift_z; - double max_z = clip_max_z - shift_z; - - Pointf3s bottom_triangles; - Pointf3s top_triangles; - - if (m_sla_caps[0].matches(min_z)) - bottom_triangles = m_sla_caps[0].triangles; - - if (m_sla_caps[1].matches(max_z)) - top_triangles = m_sla_caps[1].triangles; - - if (bottom_triangles.empty() || top_triangles.empty()) - { - const SLAPrintObject::SliceIndex& index = obj->get_slice_index(); - SLAPrintObject::SliceIndex::const_iterator it_min_z = std::find_if(index.begin(), index.end(), [min_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(min_z - id.first) < EPSILON; }); - SLAPrintObject::SliceIndex::const_iterator it_max_z = std::find_if(index.begin(), index.end(), [max_z](const SLAPrintObject::SliceIndex::value_type& id) -> bool { return std::abs(max_z - id.first) < EPSILON; }); - - if (bottom_triangles.empty() && (it_min_z != index.end())) + if (bottom_obj_triangles.empty() && (it_min_z->second.model_slices_idx < model_slices.size())) { // calculate model bottom cap - if (it_min_z->second.model_slices_idx < model_slices.size()) + const ExPolygons& polys = model_slices[it_min_z->second.model_slices_idx]; + for (const ExPolygon& poly : polys) { - const ExPolygons& polys = model_slices[it_min_z->second.model_slices_idx]; - for (const ExPolygon& poly : polys) + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) { - Polygons triangles; - poly.triangulate(&triangles); - for (const Polygon& t : triangles) + for (int v = 2; v >= 0; --v) { - for (int v = 2; v >= 0; --v) - { - bottom_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z)); - } + bottom_obj_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z)); } } } - - // calculate support bottom cap - if (it_min_z->second.support_slices_idx < support_slices.size()) - { - const ExPolygons& polys = support_slices[it_min_z->second.support_slices_idx]; - for (const ExPolygon& poly : polys) - { - Polygons triangles; - poly.triangulate(&triangles); - for (const Polygon& t : triangles) - { - for (int v = 2; v >= 0; --v) - { - bottom_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z)); - } - } - } - } - m_sla_caps[0].z = min_z; - m_sla_caps[0].triangles = bottom_triangles; } - if (top_triangles.empty() && (it_max_z != index.end())) + if (bottom_sup_triangles.empty() && (it_min_z->second.support_slices_idx < support_slices.size())) { - // calculate model top cap - if (it_max_z->second.model_slices_idx < model_slices.size()) + // calculate support bottom cap + const ExPolygons& polys = support_slices[it_min_z->second.support_slices_idx]; + for (const ExPolygon& poly : polys) { - const ExPolygons& polys = model_slices[it_max_z->second.model_slices_idx]; - for (const ExPolygon& poly : polys) + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) { - Polygons triangles; - poly.triangulate(&triangles); - for (const Polygon& t : triangles) + for (int v = 2; v >= 0; --v) { - for (int v = 0; v < 3; ++v) - { - top_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z)); - } + bottom_sup_triangles.emplace_back(to_3d(unscale(t.points[v]), min_z)); } } } - // calculate support top cap - if (it_max_z->second.support_slices_idx < support_slices.size()) - { - const ExPolygons& polys = support_slices[it_max_z->second.support_slices_idx]; - for (const ExPolygon& poly : polys) - { - Polygons triangles; - poly.triangulate(&triangles); - for (const Polygon& t : triangles) - { - for (int v = 0; v < 3; ++v) - { - top_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z)); - } - } - } - } - m_sla_caps[1].z = max_z; - m_sla_caps[1].triangles = top_triangles; + m_sla_caps[0].triangles.insert(SlaCap::ObjectIdToTrianglesMap::value_type(i, { bottom_obj_triangles, bottom_sup_triangles })); + m_sla_caps[0].z = min_z; } } - if (!bottom_triangles.empty() || !top_triangles.empty()) + if (it_max_z != index.end()) { + if (top_obj_triangles.empty() && (it_max_z->second.model_slices_idx < model_slices.size())) + { + // calculate model top cap + const ExPolygons& polys = model_slices[it_max_z->second.model_slices_idx]; + for (const ExPolygon& poly : polys) + { + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) + { + for (int v = 0; v < 3; ++v) + { + top_obj_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z)); + } + } + } + } + + if (top_sup_triangles.empty() && (it_max_z->second.support_slices_idx < support_slices.size())) + { + // calculate support top cap + const ExPolygons& polys = support_slices[it_max_z->second.support_slices_idx]; + for (const ExPolygon& poly : polys) + { + Polygons poly_triangles; + poly.triangulate(&poly_triangles); + for (const Polygon& t : poly_triangles) + { + for (int v = 0; v < 3; ++v) + { + top_sup_triangles.emplace_back(to_3d(unscale(t.points[v]), max_z)); + } + } + } + } + + m_sla_caps[1].triangles.insert(SlaCap::ObjectIdToTrianglesMap::value_type(i, { top_obj_triangles, top_sup_triangles })); + m_sla_caps[1].z = max_z; + } + } + + if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) + { + for (const InstanceTransform& inst : instance_transforms) + { + ::glPushMatrix(); + ::glTranslated(inst.offset(0), inst.offset(1), inst.offset(2)); + ::glRotatef(inst.rotation, 0.0, 0.0, 1.0); + + ::glBegin(GL_TRIANGLES); + ::glColor3f(1.0f, 0.37f, 0.0f); - for (const InstanceTransform& inst : instance_transforms) + for (const Vec3d& v : bottom_obj_triangles) { - ::glPushMatrix(); - ::glTranslated(inst.offset(0), inst.offset(1), inst.offset(2)); - ::glRotatef(inst.rotation, 0.0, 0.0, 1.0); - - ::glBegin(GL_TRIANGLES); - - if (!bottom_triangles.empty()) - { - for (const Vec3d& v : bottom_triangles) - { - ::glVertex3dv((GLdouble*)v.data()); - } - } - - if (!top_triangles.empty()) - { - for (const Vec3d& v : top_triangles) - { - ::glVertex3dv((GLdouble*)v.data()); - } - } - - ::glEnd(); - - ::glPopMatrix(); + ::glVertex3dv((GLdouble*)v.data()); } + + for (const Vec3d& v : top_obj_triangles) + { + ::glVertex3dv((GLdouble*)v.data()); + } + + ::glColor3f(1.0f, 0.0f, 0.37f); + + for (const Vec3d& v : bottom_sup_triangles) + { + ::glVertex3dv((GLdouble*)v.data()); + } + + for (const Vec3d& v : top_sup_triangles) + { + ::glVertex3dv((GLdouble*)v.data()); + } + + ::glEnd(); + + ::glPopMatrix(); } } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 44b5468a5..3076e3c5d 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -701,8 +701,14 @@ private: struct SlaCap { + struct Triangles + { + Pointf3s object; + Pointf3s suppports; + }; + typedef std::map ObjectIdToTrianglesMap; double z; - Pointf3s triangles; + ObjectIdToTrianglesMap triangles; SlaCap() { reset(); } void reset() { z = DBL_MAX; triangles.clear(); } From 1f0c12dd9ff7c7e9fbb29fdb0593011d12e63024 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Dec 2018 13:27:00 +0100 Subject: [PATCH 06/61] Do not show objects' shell in sla preview until pass slaposIndexSlices is completed --- src/slic3r/GUI/GLCanvas3D.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 9fff0945f..726cda46d 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7426,6 +7426,9 @@ void GLCanvas3D::_load_shells_sla() int obj_idx = 0; for (const SLAPrintObject* obj : print->objects()) { + if (!obj->is_step_done(slaposIndexSlices)) + continue; + unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); const ModelObject* model_obj = obj->model_object(); From 293d6ba8ad5207fc454543f2ef0926a03c2c4bcd Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Dec 2018 13:39:56 +0100 Subject: [PATCH 07/61] Suppressed import/adding of the 3mf which contains multi-part objects + Try to fix the wrong extruder selection under OSX --- src/slic3r/GUI/GUI_ObjectList.cpp | 56 ++++++++++++++++++++++--------- src/slic3r/GUI/GUI_ObjectList.hpp | 10 +++--- src/slic3r/GUI/Plater.cpp | 12 +++++++ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 3a7727cfe..4996bbd5d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -68,24 +68,26 @@ ObjectList::ObjectList(wxWindow* parent) : #ifdef __WXMSW__ // Extruder value changed - Bind(wxEVT_CHOICE, [this](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); }); +// Bind(wxEVT_CHOICE, [this](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); }); GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { set_tooltip_for_item(/*event.GetPosition()*/get_mouse_position_in_control()); event.Skip(); }); -#else +// #else // equivalent to wxEVT_CHOICE on __WXMSW__ - Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [this](wxDataViewEvent& e) { item_value_change(e); }); +// Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [this](wxDataViewEvent& e) { item_value_change(e); }); #endif //__WXMSW__ - 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(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()); }); + Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); - Bind(wxEVT_DATAVIEW_ITEM_START_EDITING, &ObjectList::OnStartEditing, this); +// Bind(wxEVT_DATAVIEW_ITEM_START_EDITING, &ObjectList::OnStartEditing, this); +// Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); + Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); } ObjectList::~ObjectList() @@ -276,9 +278,9 @@ void ObjectList::selection_changed() part_selection_changed(); -#ifdef __WXOSX__ - update_extruder_in_config(m_selected_extruder); -#endif //__WXOSX__ +// #ifdef __WXOSX__ +// update_extruder_in_config(m_selected_extruder); +// #endif //__WXOSX__ } void ObjectList::context_menu() @@ -1623,11 +1625,35 @@ void ObjectList::update_settings_items() UnselectAll(); } -void ObjectList::OnStartEditing(wxDataViewEvent &event) +// void ObjectList::OnEditingDone(wxDataViewEvent &event) +// { +// m_selected_extruder = event.GetValue().GetString(); +// } + +void ObjectList::ItemValueChanged(wxDataViewEvent &event) { - const auto item_type = m_objects_model->GetItemType(event.GetItem()); - if ( !(item_type&(itObject|itVolume)) ) - event.Veto(); + const wxDataViewItem item = event.GetItem(); + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + const int obj_idx = m_objects_model->GetIdByItem(item); + m_config = &(*m_objects)[obj_idx]->config; + } + else { + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int volume_id = m_objects_model->GetVolumeIdByItem(item); + m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + + wxVariant variant; + m_objects_model->GetValue(variant, event.GetItem(), 1); + const wxString sel_extr = variant.GetString(); + if (!m_config || sel_extr.empty()) + return; + + int extruder = sel_extr.size() > 1 ? 0 : atoi(sel_extr.c_str()); + m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); + + // update scene + wxGetApp().plater()->update(); } } //namespace GUI diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index fd707e2eb..2e946a28d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -80,9 +80,9 @@ class ObjectList : public wxDataViewCtrl bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again -#ifdef __WXOSX__ - wxString m_selected_extruder = ""; -#endif //__WXOSX__ +// #ifdef __WXOSX__ +// wxString m_selected_extruder = ""; +// #endif //__WXOSX__ bool m_parts_changed = false; bool m_part_settings_changed = false; @@ -202,8 +202,8 @@ public: void update_settings_items(); private: - void OnStartEditing(wxDataViewEvent &event); - +// void OnEditingDone(wxDataViewEvent &event); + void ItemValueChanged(wxDataViewEvent &event); }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 28a6a5501..5dc685111 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1453,6 +1453,18 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } + // check multi-part object adding for the SLA-printing + if (printer_technology == ptSLA) + { + for (auto obj : model.objects) + if ( obj->volumes.size()>1 ) { + Slic3r::GUI::show_error(nullptr, + wxString::Format(_(L("You can't to add the object(s) from %s because of one or some of them is(are) multi-part")), + filename.string())); + return std::vector(); + } + } + if (one_by_one) { auto loaded_idxs = load_model_objects(model.objects); obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); From eb1518bf312aee6ddebe493dea06816cdc3313e0 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Dec 2018 13:57:43 +0100 Subject: [PATCH 08/61] Removed out of bed detection for sla pad --- src/slic3r/GUI/3DScene.cpp | 26 +++++++++++++------------- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ac9755e1c..09c3443c3 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -870,8 +870,8 @@ void GLVolumeCollection::load_object_auxiliary( // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true); v.is_modifier = false; - v.shader_outside_printer_detection_enabled = true; - v.set_instance_transformation(model_instance.get_transformation()); + v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree); + v.set_instance_transformation(model_instance.get_transformation()); // Leave the volume transformation at identity. // v.set_volume_transformation(model_volume->get_transformation()); } @@ -1039,20 +1039,20 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M for (GLVolume* volume : this->volumes) { - if ((volume != nullptr) && !volume->is_modifier && (!volume->is_wipe_tower || (volume->is_wipe_tower && volume->shader_outside_printer_detection_enabled))) - { - const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); - bool contained = print_volume.contains(bb); - all_contained &= contained; + if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled)) + continue; - volume->is_outside = !contained; + const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box(); + bool contained = print_volume.contains(bb); + all_contained &= contained; - if ((state == ModelInstance::PVS_Inside) && volume->is_outside) - state = ModelInstance::PVS_Fully_Outside; + volume->is_outside = !contained; - if ((state == ModelInstance::PVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) - state = ModelInstance::PVS_Partly_Outside; - } + if ((state == ModelInstance::PVS_Inside) && volume->is_outside) + state = ModelInstance::PVS_Fully_Outside; + + if ((state == ModelInstance::PVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) + state = ModelInstance::PVS_Partly_Outside; } if (out_state != nullptr) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 726cda46d..721969fa5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -7479,7 +7479,7 @@ void GLCanvas3D::_load_shells_sla() else v.indexed_vertex_array.load_mesh_flat_shading(mesh); - v.shader_outside_printer_detection_enabled = true; + v.shader_outside_printer_detection_enabled = false; v.composite_id.volume_id = -1; v.set_instance_offset(offset); v.set_instance_rotation(rotation); From 5e4c6c8e2d44f9ebd223cb4c887fb4d3f45a16b9 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Dec 2018 14:13:01 +0100 Subject: [PATCH 09/61] Fixed build under OSX --- src/slic3r/GUI/GUI_ObjectList.cpp | 53 ++++++++++++++----------------- src/slic3r/GUI/GUI_ObjectList.hpp | 2 +- 2 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 4996bbd5d..2b74b28d6 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -222,12 +222,28 @@ void ObjectList::set_extruder_column_hidden(bool hide) GetColumn(1)->SetHidden(hide); } -void ObjectList::update_extruder_in_config(const wxString& selection) +void ObjectList::update_extruder_in_config(const wxDataViewItem& item/*wxString& selection*/) { + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + const int obj_idx = m_objects_model->GetIdByItem(item); + m_config = &(*m_objects)[obj_idx]->config; + } + else { + const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); + const int volume_id = m_objects_model->GetVolumeIdByItem(item); + if (obj_idx < 0 || volume_id < 0) + return; + m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; + } + + wxVariant variant; + m_objects_model->GetValue(variant, item, 1); + const wxString selection = variant.GetString(); + if (!m_config || selection.empty()) return; - int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); + const int extruder = selection.size() > 1 ? 0 : atoi(selection.c_str()); m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); // update scene @@ -366,11 +382,11 @@ void ObjectList::item_value_change(wxDataViewEvent& event) { wxVariant variant; m_objects_model->GetValue(variant, event.GetItem(), 1); -#ifdef __WXOSX__ - m_selected_extruder = variant.GetString(); -#else // --> for Linux - update_extruder_in_config(variant.GetString()); -#endif //__WXOSX__ +// #ifdef __WXOSX__ +// m_selected_extruder = variant.GetString(); +// #else // --> for Linux +// update_extruder_in_config(variant.GetString()); +// #endif //__WXOSX__ } } @@ -1632,28 +1648,7 @@ void ObjectList::update_settings_items() void ObjectList::ItemValueChanged(wxDataViewEvent &event) { - const wxDataViewItem item = event.GetItem(); - if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { - const int obj_idx = m_objects_model->GetIdByItem(item); - m_config = &(*m_objects)[obj_idx]->config; - } - else { - const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetParent(item)); - const int volume_id = m_objects_model->GetVolumeIdByItem(item); - m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; - } - - wxVariant variant; - m_objects_model->GetValue(variant, event.GetItem(), 1); - const wxString sel_extr = variant.GetString(); - if (!m_config || sel_extr.empty()) - return; - - int extruder = sel_extr.size() > 1 ? 0 : atoi(sel_extr.c_str()); - m_config->set_key_value("extruder", new ConfigOptionInt(extruder)); - - // update scene - wxGetApp().plater()->update(); + update_extruder_in_config(event.GetItem()); } } //namespace GUI diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 2e946a28d..9d959c5b7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -105,7 +105,7 @@ public: // show/hide "Extruder" column for Objects List void set_extruder_column_hidden(bool hide); // update extruder in current config - void update_extruder_in_config(const wxString& selection); + void update_extruder_in_config(const wxDataViewItem& item/*wxString& selection*/); void init_icons(); From 92ace1c97acce12ed2e48c28df963774ec68d125 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 10 Dec 2018 14:38:49 +0100 Subject: [PATCH 10/61] fix for missing top ring in the raster. SPE-669 --- src/libslic3r/SLA/SLASupportTree.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 57cf5d14e..f66351bdb 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -743,8 +743,11 @@ public: // WITH THE PAD double full_height() const { + if(merged_mesh().empty() && !pad().empty()) + return pad().cfg.min_wall_height_mm; + double h = mesh_height(); - if(!pad().empty()) h += pad().cfg.min_wall_height_mm / 2; + if(!pad().empty()) h += sla::get_pad_elevation(pad().cfg); return h; } From 0420d76037640f3ddc799f4c8dda776e460ad91a Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Mon, 10 Dec 2018 16:09:20 +0100 Subject: [PATCH 11/61] Fixed rotation of volumes contained in rotated instances --- src/slic3r/GUI/GLCanvas3D.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 721969fa5..5df8c61c0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1531,7 +1531,8 @@ void GLCanvas3D::Selection::rotate(const Vec3d& rotation, bool local) #if ENABLE_WORLD_ROTATIONS { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + const Transform3d& inst_m = m_cache.volumes_data[i].get_instance_rotation_matrix(); + Vec3d new_rotation = Geometry::extract_euler_angles(inst_m.inverse() * m * inst_m * m_cache.volumes_data[i].get_volume_rotation_matrix()); (*m_volumes)[i]->set_volume_rotation(new_rotation); } #else From d7db1cdc734c0d72c3c955b55e88638ce3ece4a2 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Mon, 10 Dec 2018 17:00:28 +0100 Subject: [PATCH 12/61] Fixed extruder number updating after changing of the extruder count --- src/slic3r/GUI/GUI_ObjectList.cpp | 81 ++++++++++++++++++------------- src/slic3r/GUI/GUI_ObjectList.hpp | 10 ++-- 2 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2b74b28d6..b08ec0a56 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -67,16 +67,10 @@ ObjectList::ObjectList(wxWindow* parent) : Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX #ifdef __WXMSW__ - // Extruder value changed -// Bind(wxEVT_CHOICE, [this](wxCommandEvent& event) { update_extruder_in_config(event.GetString()); }); - GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { set_tooltip_for_item(/*event.GetPosition()*/get_mouse_position_in_control()); event.Skip(); }); -// #else - // equivalent to wxEVT_CHOICE on __WXMSW__ -// Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, [this](wxDataViewEvent& e) { item_value_change(e); }); #endif //__WXMSW__ Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, [this](wxDataViewEvent& e) { on_begin_drag(e); }); @@ -201,12 +195,54 @@ wxDataViewColumn* ObjectList::create_objects_list_extruder_column(int extruders_ return column; } +void ObjectList::update_extruder_values_for_items(const int max_extruder) +{ + for (int i = 0; i < m_objects->size(); ++i) + { + wxDataViewItem item = m_objects_model->GetItemById(i); + if (!item) continue; + + auto object = (*m_objects)[i]; + wxString extruder; + if (!object->config.has("extruder") || + object->config.option("extruder")->value > max_extruder) + extruder = "default"; + else + extruder = wxString::Format("%d", object->config.option("extruder")->value); + + m_objects_model->SetValue(extruder, item, 1); + + if (object->volumes.size() > 1) { + for (auto id = 0; id < object->volumes.size(); id++) { + item = m_objects_model->GetItemByVolumeId(i, id); + if (!item) continue; + if (!object->volumes[id]->config.has("extruder") || + object->volumes[id]->config.option("extruder")->value > max_extruder) + extruder = "default"; + else + extruder = wxString::Format("%d", object->volumes[id]->config.option("extruder")->value); + + m_objects_model->SetValue(extruder, item, 1); + } + } + } +} + void ObjectList::update_objects_list_extruder_column(int extruders_count) { if (!this) return; // #ys_FIXME if (wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA) extruders_count = 1; + wxDataViewChoiceRenderer* ch_render = dynamic_cast(GetColumn(1)->GetRenderer()); + if (ch_render->GetChoices().GetCount() - 1 == extruders_count) + return; + + m_prevent_update_extruder_in_config = true; + + if (m_objects && extruders_count > 1) + update_extruder_values_for_items(extruders_count); + // delete old 2nd column DeleteColumn(GetColumn(1)); // insert new created 3rd column @@ -215,6 +251,8 @@ void ObjectList::update_objects_list_extruder_column(int extruders_count) set_extruder_column_hidden(extruders_count <= 1); //a workaround for a wrong last column width updating under OSX GetColumn(2)->SetWidth(25); + + m_prevent_update_extruder_in_config = false; } void ObjectList::set_extruder_column_hidden(bool hide) @@ -222,8 +260,10 @@ void ObjectList::set_extruder_column_hidden(bool hide) GetColumn(1)->SetHidden(hide); } -void ObjectList::update_extruder_in_config(const wxDataViewItem& item/*wxString& selection*/) +void ObjectList::update_extruder_in_config(const wxDataViewItem& item) { + if (m_prevent_update_extruder_in_config) + return; if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { const int obj_idx = m_objects_model->GetIdByItem(item); m_config = &(*m_objects)[obj_idx]->config; @@ -376,20 +416,6 @@ void ObjectList::key_event(wxKeyEvent& event) event.Skip(); } -void ObjectList::item_value_change(wxDataViewEvent& event) -{ - if (event.GetColumn() == 1) - { - wxVariant variant; - m_objects_model->GetValue(variant, event.GetItem(), 1); -// #ifdef __WXOSX__ -// m_selected_extruder = variant.GetString(); -// #else // --> for Linux -// update_extruder_in_config(variant.GetString()); -// #endif //__WXOSX__ - } -} - struct draging_item_data { int obj_idx; @@ -589,9 +615,6 @@ void ObjectList::get_settings_choice(const wxString& category_name) const auto settings_item = m_objects_model->GetSettingsItem(item); select_item(settings_item ? settings_item : m_objects_model->AddSettingsChild(item)); -#ifndef __WXOSX__ -// part_selection_changed(); -#endif //no __WXOSX__ } else { auto panel = wxGetApp().sidebar().scrolled_panel(); @@ -765,11 +788,6 @@ void ObjectList::load_subobject(int type) if (i == part_names.size() - 1) select_item(sel_item); } - -#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME -// selection_changed(); -#endif //no __WXOSX__//__WXMSW__ - } void ObjectList::load_part( ModelObject* model_object, @@ -1641,11 +1659,6 @@ void ObjectList::update_settings_items() UnselectAll(); } -// void ObjectList::OnEditingDone(wxDataViewEvent &event) -// { -// m_selected_extruder = event.GetValue().GetString(); -// } - void ObjectList::ItemValueChanged(wxDataViewEvent &event) { update_extruder_in_config(event.GetItem()); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 9d959c5b7..1cbadfb4f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -80,9 +80,10 @@ class ObjectList : public wxDataViewCtrl bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler // calls this method again and again and again -// #ifdef __WXOSX__ -// wxString m_selected_extruder = ""; -// #endif //__WXOSX__ + + bool m_prevent_update_extruder_in_config = false; // We use this flag to avoid updating of the extruder value in config + // during updating of the extruder count. + bool m_parts_changed = false; bool m_part_settings_changed = false; @@ -106,6 +107,7 @@ public: void set_extruder_column_hidden(bool hide); // update extruder in current config void update_extruder_in_config(const wxDataViewItem& item/*wxString& selection*/); + void update_extruder_values_for_items(const int max_extruder); void init_icons(); @@ -115,7 +117,6 @@ public: void context_menu(); void show_context_menu(); void key_event(wxKeyEvent& event); - void item_value_change(wxDataViewEvent& event); void on_begin_drag(wxDataViewEvent &event); void on_drop_possible(wxDataViewEvent &event); @@ -202,7 +203,6 @@ public: void update_settings_items(); private: -// void OnEditingDone(wxDataViewEvent &event); void ItemValueChanged(wxDataViewEvent &event); }; From 05fef33b1045063195384e8d45460692f9c8ebfe Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Dec 2018 08:53:18 +0100 Subject: [PATCH 13/61] Update extruder value for adding objects --- src/slic3r/GUI/GUI_ObjectList.cpp | 4 +++- src/slic3r/GUI/wxExtensions.cpp | 5 +++-- src/slic3r/GUI/wxExtensions.hpp | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b08ec0a56..b85c2af1a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1174,7 +1174,9 @@ void ObjectList::add_object_to_list(size_t obj_idx) { auto model_object = (*m_objects)[obj_idx]; wxString item_name = model_object->name; - auto item = m_objects_model->Add(item_name); + const auto item = m_objects_model->Add(item_name, + !model_object->config.has("extruder") ? 0 : + model_object->config.option("extruder")->value); // Add error icon if detected auto-repaire auto stats = model_object->volumes[0]->mesh.stl.stats; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index c20ca11dc..98a246f10 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -454,9 +454,10 @@ PrusaObjectDataViewModel::~PrusaObjectDataViewModel() m_bitmap_cache = nullptr; } -wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name) +wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int extruder) { - auto root = new PrusaObjectDataViewModelNode(name); + const wxString extruder_str = extruder == 0 ? "default" : wxString::Format("%d", extruder); + auto root = new PrusaObjectDataViewModelNode(name, extruder_str); m_objects.push_back(root); // notify control wxDataViewItem child((void*)root); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index c59ad6f20..6f87579e2 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -222,7 +222,8 @@ class PrusaObjectDataViewModelNode size_t m_volumes_cnt = 0; std::vector< std::string > m_opt_categories; public: - PrusaObjectDataViewModelNode(const wxString &name) { + PrusaObjectDataViewModelNode(const wxString &name, + const wxString& extruder) { m_parent = NULL; m_name = name; m_type = itObject; @@ -232,6 +233,7 @@ public: // it will be produce "segmentation fault" m_container = true; #endif //__WXGTK__ + m_extruder = extruder; set_object_action_icon(); } @@ -438,7 +440,7 @@ public: PrusaObjectDataViewModel(); ~PrusaObjectDataViewModel(); - wxDataViewItem Add(const wxString &name); + wxDataViewItem Add(const wxString &name, const int extruder); wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item, const wxString &name, const int volume_type, From edd79883a1f4cec8ede773e19b72e8e031d2dfeb Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Dec 2018 09:37:58 +0100 Subject: [PATCH 14/61] Try to fix DnD under OSX + try to fix wxEVT_CHAR under OSX + some code-rebase --- src/slic3r/GUI/GUI_ObjectList.cpp | 68 ++++++++++++++++++------------- src/slic3r/GUI/GUI_ObjectList.hpp | 16 ++++---- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b85c2af1a..85236b2a1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -60,11 +60,7 @@ ObjectList::ObjectList(wxWindow* parent) : #endif //__WXMSW__ }); - Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, [this](wxDataViewEvent& event) { - context_menu(); - }); - - Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX +// Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX #ifdef __WXMSW__ GetMainWindow()->Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { @@ -73,15 +69,15 @@ ObjectList::ObjectList(wxWindow* parent) : }); #endif //__WXMSW__ - 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(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &ObjectList::OnContextMenu, this); + + Bind(wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, &ObjectList::OnBeginDrag, this); + Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, &ObjectList::OnDropPossible, this); + Bind(wxEVT_DATAVIEW_ITEM_DROP, &ObjectList::OnDrop, this); + + Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); - -// Bind(wxEVT_DATAVIEW_ITEM_START_EDITING, &ObjectList::OnStartEditing, this); -// Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); - Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); } ObjectList::~ObjectList() @@ -97,6 +93,10 @@ void ObjectList::create_objects_ctrl() // 2. change it to the normal min value (200) after first whole App updating/layouting SetMinSize(wxSize(-1, 1500)); // #ys_FIXME +#ifdef __WXOSX__ + Connect(wxEVT_CHAR, wxKeyEventHandler(ObjectList::OnChar), NULL, this); +#endif //__WXOSX__ + m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(this, 1, wxGROW | wxLEFT, 20); @@ -255,7 +255,7 @@ void ObjectList::update_objects_list_extruder_column(int extruders_count) m_prevent_update_extruder_in_config = false; } -void ObjectList::set_extruder_column_hidden(bool hide) +void ObjectList::set_extruder_column_hidden(const bool hide) const { GetColumn(1)->SetHidden(hide); } @@ -333,13 +333,21 @@ void ObjectList::selection_changed() } part_selection_changed(); - -// #ifdef __WXOSX__ -// update_extruder_in_config(m_selected_extruder); -// #endif //__WXOSX__ } -void ObjectList::context_menu() +void ObjectList::OnChar(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_DELETE && event.GetKeyCode() == WXK_BACK){ + printf("WXK_BACK\n"); + remove(); + } + else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) + select_item_all_children(); + else + event.Skip(); +} + +void ObjectList::OnContextMenu(wxDataViewEvent&) { wxDataViewItem item; wxDataViewColumn* col; @@ -422,7 +430,7 @@ struct draging_item_data int vol_idx; }; -void ObjectList::on_begin_drag(wxDataViewEvent &event) +void ObjectList::OnBeginDrag(wxDataViewEvent &event) { wxDataViewItem item(event.GetItem()); @@ -437,7 +445,7 @@ void ObjectList::on_begin_drag(wxDataViewEvent &event) /* Under MSW or OSX, DnD moves an item to the place of another selected item * But under GTK, DnD moves an item between another two items. * And as a result - call EVT_CHANGE_SELECTION to unselect all items. - * To prevent such behavior use g_prevent_list_events + * To prevent such behavior use m_prevent_list_events **/ m_prevent_list_events = true;//it's needed for GTK @@ -445,29 +453,31 @@ void ObjectList::on_begin_drag(wxDataViewEvent &event) obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item))); event.SetDataObject(obj); event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move; + printf("BeginDrag\n"); } -void ObjectList::on_drop_possible(wxDataViewEvent &event) +void ObjectList::OnDropPossible(wxDataViewEvent &event) { wxDataViewItem item(event.GetItem()); // only allow drags for item or background, not containers - if (item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || - event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->GetItemType(item) != itVolume) + if (event.GetDataFormat() != wxDF_UNICODETEXT || item.IsOk() && + (m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->GetItemType(item) != itVolume)) event.Veto(); + printf("DropPossible\n"); } -void ObjectList::on_drop(wxDataViewEvent &event) +void ObjectList::OnDrop(wxDataViewEvent &event) { wxDataViewItem item(event.GetItem()); - // only allow drops for item, not containers - if (m_selected_object_id < 0 || - item.IsOk() && m_objects_model->GetParent(item) == wxDataViewItem(0) || - event.GetDataFormat() != wxDF_UNICODETEXT || m_objects_model->GetItemType(item) != itVolume) { + if (m_selected_object_id < 0 || event.GetDataFormat() != wxDF_UNICODETEXT || + item.IsOk() && ( m_objects_model->GetParent(item) == wxDataViewItem(0) || + m_objects_model->GetItemType(item) != itVolume) ) { event.Veto(); return; } + printf("Drop\n"); wxTextDataObject obj; obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer()); @@ -495,6 +505,8 @@ void ObjectList::on_drop(wxDataViewEvent &event) m_parts_changed = true; parts_changed(m_selected_object_id); + printf("DropCompleted\n"); + // m_prevent_list_events = false; } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 1cbadfb4f..5cb76b8aa 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -104,9 +104,9 @@ public: wxDataViewColumn* create_objects_list_extruder_column(int extruders_count); void update_objects_list_extruder_column(int extruders_count); // show/hide "Extruder" column for Objects List - void set_extruder_column_hidden(bool hide); + void set_extruder_column_hidden(const bool hide) const; // update extruder in current config - void update_extruder_in_config(const wxDataViewItem& item/*wxString& selection*/); + void update_extruder_in_config(const wxDataViewItem& item); void update_extruder_values_for_items(const int max_extruder); void init_icons(); @@ -114,14 +114,9 @@ public: void set_tooltip_for_item(const wxPoint& pt); void selection_changed(); - void context_menu(); void show_context_menu(); void key_event(wxKeyEvent& event); - void on_begin_drag(wxDataViewEvent &event); - void on_drop_possible(wxDataViewEvent &event); - void on_drop(wxDataViewEvent &event); - void get_settings_choice(const wxString& category_name); void append_menu_item_add_generic(wxMenuItem* menu, const int type); void append_menu_items_add_volume(wxMenu* menu); @@ -203,6 +198,13 @@ public: void update_settings_items(); private: + void OnChar(wxKeyEvent& event); + void OnContextMenu(wxDataViewEvent &event); + + void OnBeginDrag(wxDataViewEvent &event); + void OnDropPossible(wxDataViewEvent &event); + void OnDrop(wxDataViewEvent &event); + void ItemValueChanged(wxDataViewEvent &event); }; From 5e077c5edfc5a6aa3a93c0caf1530f2ecfbc76c4 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 11 Dec 2018 09:43:10 +0100 Subject: [PATCH 15/61] Fixed GLCanvas3D::Selection::is_single_full_instance() --- src/slic3r/GUI/GLCanvas3D.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 5df8c61c0..2ed02fd46 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1404,6 +1404,9 @@ bool GLCanvas3D::Selection::is_single_full_instance() const if (m_type == SingleFullInstance) return true; + if (m_type == SingleFullObject) + return false; + if (m_list.empty() || m_volumes->empty()) return false; From 984b8b79b0b5d09c6e577053118d89217727203c Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Tue, 11 Dec 2018 10:35:12 +0100 Subject: [PATCH 16/61] Another fix into GLCanvas3D::Selection::is_single_full_instance() --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2ed02fd46..aad3e288a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1405,7 +1405,7 @@ bool GLCanvas3D::Selection::is_single_full_instance() const return true; if (m_type == SingleFullObject) - return false; + return get_instance_idx() != -1; if (m_list.empty() || m_volumes->empty()) return false; From e0cf7ecd22dc7afef1cdc1eee8f6d3e416de7243 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Dec 2018 13:16:09 +0100 Subject: [PATCH 17/61] WIP: Command line slicing for SLA. Removed some layer height editing bindings from Perl. --- src/slic3r.cpp | 38 ++++++++++++++++++++++++++------------ xs/xsp/Model.xsp | 5 ----- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 6ff46e1ab..59b23c133 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -27,6 +27,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" +#include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Format/3mf.hpp" #include "libslic3r/Utils.hpp" @@ -105,7 +106,7 @@ int main(int argc, char **argv) } // load config files supplied via --load for (const std::string &file : cli_config.load.values) { - if (!boost::filesystem::exists(file)) { + if (! boost::filesystem::exists(file)) { boost::nowide::cout << "No such file: " << file << std::endl; exit(1); } @@ -206,22 +207,35 @@ int main(int argc, char **argv) // lower.mesh().write_binary((outfile + "_lower.stl").c_str()); } } else if (cli_config.slice) { + PrinterTechnology printer_technology = print_config.option>("printer_technology", true)->value; std::string outfile = cli_config.output.value; - Print print; + Print fff_print; + SLAPrint sla_print; + PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); if (! cli_config.dont_arrange) { - model.arrange_objects(print.config().min_object_distance()); + //FIXME make the min_object_distance configurable. + model.arrange_objects(fff_print.config().min_object_distance()); model.center_instances_around_point(cli_config.print_center); } - if (outfile.empty()) - outfile = model.propose_export_file_name() + ".gcode"; - for (auto* mo : model.objects) - print.auto_assign_extruders(mo); + if (outfile.empty()) { + outfile = model.propose_export_file_name(); + outfile += (printer_technology == ptFFF) ? ".gcode" : ".zip"; + } + if (printer_technology == ptFFF) { + for (auto* mo : model.objects) + fff_print.auto_assign_extruders(mo); + } print_config.normalize(); - print.apply(model, print_config); - std::string err = print.validate(); - if (err.empty()) - print.export_gcode(outfile, nullptr); - else + print->apply(model, print_config); + std::string err = print->validate(); + if (err.empty()) { + if (printer_technology == ptFFF) { + fff_print.export_gcode(outfile, nullptr); + } else { + assert(printer_technology == ptSLA); + //FIXME add the output here + } + } else std::cerr << err << "\n"; } else { boost::nowide::cerr << "error: command not supported" << std::endl; diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 6091d00ad..8f1d88c74 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -211,11 +211,6 @@ ModelMaterial::attributes() void set_layer_height_ranges(t_layer_height_ranges ranges) %code%{ THIS->layer_height_ranges = ranges; %}; - std::vector layer_height_profile() - %code%{ RETVAL = THIS->layer_height_profile_valid ? THIS->layer_height_profile : std::vector(); %}; - void set_layer_height_profile(std::vector profile) - %code%{ THIS->layer_height_profile = profile; THIS->layer_height_profile_valid = true; %}; - Ref origin_translation() %code%{ RETVAL = &THIS->origin_translation; %}; void set_origin_translation(Vec3d* point) From d7bc1410eea26c7b44f4657964a57b50d64894e5 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Dec 2018 13:34:37 +0100 Subject: [PATCH 18/61] Update value inside TextCtrl & SpinCtrl after wxEVT_KILL_FOCES instead of wxEVT_TEXT (or wxEVT_TEXT_ENTER) --- src/slic3r/GUI/Field.cpp | 40 +++++++++++++---------- src/slic3r/GUI/Field.hpp | 12 +++---- src/slic3r/GUI/GUI_ObjectList.cpp | 7 ++-- src/slic3r/GUI/GUI_ObjectManipulation.cpp | 21 ++++++++++-- src/slic3r/GUI/GUI_ObjectSettings.hpp | 2 +- src/slic3r/GUI/OptionsGroup.cpp | 17 +++++++--- src/slic3r/GUI/OptionsGroup.hpp | 14 +++----- src/slic3r/GUI/Tab.cpp | 2 +- 8 files changed, 68 insertions(+), 47 deletions(-) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 14f409698..29218eaf6 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -74,7 +74,7 @@ void Field::on_kill_focus(wxEvent& event) event.Skip(); // call the registered function if it is available if (m_on_kill_focus!=nullptr) - m_on_kill_focus(); + m_on_kill_focus(m_opt_id); } void Field::on_change_field() @@ -125,9 +125,9 @@ void Field::get_value_by_opt_type(wxString& str) case coPercents: case coFloats: case coFloat:{ - if (m_opt.type == coPercent && str.Last() == '%') + if (m_opt.type == coPercent && !str.IsEmpty() && str.Last() == '%') str.RemoveLast(); - else if (str.Last() == '%') { + else if (!str.IsEmpty() && str.Last() == '%') { wxString label = m_Label->GetLabel(); if (label.Last() == '\n') label.RemoveLast(); while (label.Last() == ' ') label.RemoveLast(); @@ -162,7 +162,7 @@ void Field::get_value_by_opt_type(wxString& str) } } -bool TextCtrl::is_defined_input_value() +bool TextCtrl::is_defined_input_value() const { if (static_cast(window)->GetValue().empty() && m_opt.type != coString && m_opt.type != coStrings) return false; @@ -216,7 +216,7 @@ void TextCtrl::BUILD() { break; } - const long style = m_opt.multiline ? wxTE_MULTILINE : 0 | m_process_enter ? wxTE_PROCESS_ENTER : 0; + const long style = m_opt.multiline ? wxTE_MULTILINE : 0; auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); temp->SetToolTip(get_tooltip_text(text_value)); @@ -240,17 +240,13 @@ void TextCtrl::BUILD() { e.Skip(); temp->GetToolTip()->Enable(true); #endif // __WXGTK__ - if (!is_defined_input_value()) +// if (!is_defined_input_value()) + if (is_defined_input_value()) + on_change_field(); + else on_kill_focus(e); }), temp->GetId()); - - if (m_process_enter) { - temp->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent& evt) { - if(is_defined_input_value()) - on_change_field(); - }), temp->GetId()); - } - else { + /* temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt) { #ifdef __WXGTK__ @@ -267,8 +263,7 @@ void TextCtrl::BUILD() { temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this); temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this); #endif //__WXGTK__ - } - +*/ // select all text using Ctrl+A temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) { @@ -371,7 +366,15 @@ void SpinCtrl::BUILD() { 0, min_val, max_val, default_value); // temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId()); - temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { on_kill_focus(e); }), temp->GetId()); + temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) + { + if (tmp_value < 0) + on_kill_focus(e); + else { + e.Skip(); + on_change_field(); + } + }), temp->GetId()); temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { // # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value @@ -382,7 +385,8 @@ void SpinCtrl::BUILD() { std::string value = e.GetString().utf8_str().data(); if (is_matched(value, "^\\d+$")) tmp_value = std::stoi(value); - on_change_field(); + else tmp_value = -9999; +// on_change_field(); // # We don't reset tmp_value here because _on_change might put callbacks // # in the CallAfter queue, and we want the tmp value to be available from // # them as well. diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index aef2094a7..66a71b34d 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -29,8 +29,8 @@ namespace Slic3r { namespace GUI { class Field; using t_field = std::unique_ptr; -using t_kill_focus = std::function; -using t_change = std::function; +using t_kill_focus = std::function; +using t_change = std::function; using t_back_to_init = std::function; wxString double_to_string(double const value, const int max_precision = 4); @@ -139,10 +139,9 @@ public: /// Factory method for generating new derived classes. template - static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id, const bool process_enter = false)// interface for creating shared objects + static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id)// interface for creating shared objects { auto p = Slic3r::make_unique(parent, opt, id); - p->m_process_enter = process_enter; p->PostInitialize(); return std::move(p); //!p; } @@ -223,9 +222,6 @@ protected: // current value boost::any m_value; - //this variable shows a mode of a call of the on_change function - bool m_process_enter { false }; - friend class OptionsGroup; }; @@ -265,7 +261,7 @@ public: } boost::any& get_value() override; - bool is_defined_input_value(); + bool is_defined_input_value() const ; virtual void enable(); virtual void disable(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 85236b2a1..ba98573bf 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -453,7 +453,6 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item))); event.SetDataObject(obj); event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move; - printf("BeginDrag\n"); } void ObjectList::OnDropPossible(wxDataViewEvent &event) @@ -464,7 +463,6 @@ void ObjectList::OnDropPossible(wxDataViewEvent &event) if (event.GetDataFormat() != wxDF_UNICODETEXT || item.IsOk() && (m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->GetItemType(item) != itVolume)) event.Veto(); - printf("DropPossible\n"); } void ObjectList::OnDrop(wxDataViewEvent &event) @@ -477,13 +475,14 @@ void ObjectList::OnDrop(wxDataViewEvent &event) event.Veto(); return; } - printf("Drop\n"); wxTextDataObject obj; obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer()); + printf("Drop\n"); int from_volume_id = std::stoi(obj.GetText().ToStdString()); int to_volume_id = m_objects_model->GetVolumeIdByItem(item); + printf("from %d to %d\n", from_volume_id, to_volume_id); #ifdef __WXGTK__ /* Under GTK, DnD moves an item between another two items. @@ -498,10 +497,12 @@ void ObjectList::OnDrop(wxDataViewEvent &event) int cnt = 0; for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) std::swap(volumes[id], volumes[id + delta]); + printf("Volumes are swapped\n"); select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, m_objects_model->GetParent(item))); + printf("ItemChildren are Reorganized\n"); m_parts_changed = true; parts_changed(m_selected_object_id); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 28d709ef7..d12aabf9e 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -21,9 +21,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_og->set_name(_(L("Object Manipulation"))); m_og->label_width = 100; m_og->set_grid_vgap(5); - m_og->set_process_enter(); // We need to update new values only after press ENTER - m_og->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_og->m_on_change = [this](const std::string& opt_key, const boost::any& value) { std::vector axes{ "_x", "_y", "_z" }; if (opt_key == "scale_unit") { @@ -54,6 +53,24 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : change_scale_value(new_value); }; + m_og->m_fill_empty_value = [this](const std::string& opt_key) + { + if (opt_key == "scale_unit") + return; + + std::string param; + std::copy(opt_key.begin(), opt_key.end() - 2, std::back_inserter(param)); + if (param == "position") { + int axis = opt_key.back() == 'x' ? 0 : + opt_key.back() == 'y' ? 1 : 2; + + m_og->set_value(opt_key, double_to_string(cache_position(axis))); + return; + } + + m_og->set_value(opt_key, double_to_string(0.0)); + }; + ConfigOptionDef def; // Objects(sub-objects) name diff --git a/src/slic3r/GUI/GUI_ObjectSettings.hpp b/src/slic3r/GUI/GUI_ObjectSettings.hpp index 7b58d4c4e..19140efe3 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.hpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.hpp @@ -17,7 +17,7 @@ protected: wxWindow* m_parent; public: OG_Settings(wxWindow* parent, const bool staticbox); - ~OG_Settings() {} + virtual ~OG_Settings() {} virtual bool IsShown(); virtual void Show(const bool show); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index be9a4e9cd..512bcb4ac 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -44,7 +44,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co case coPercents: case coString: case coStrings: - m_fields.emplace(id, std::move(TextCtrl::Create(parent(), opt, id, process_enter))); + m_fields.emplace(id, std::move(TextCtrl::Create(parent(), opt, id))); break; case coBool: case coBools: @@ -67,16 +67,16 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co } // Grab a reference to fields for convenience const t_field& field = m_fields[id]; - field->m_on_change = [this](std::string opt_id, boost::any value) { + field->m_on_change = [this](const std::string& opt_id, const boost::any& value) { //! This function will be called from Field. //! Call OptionGroup._on_change(...) if (!m_disabled) this->on_change_OG(opt_id, value); }; - field->m_on_kill_focus = [this]() { + field->m_on_kill_focus = [this](const std::string& opt_id) { //! This function will be called from Field. if (!m_disabled) - this->on_kill_focus(); + this->on_kill_focus(opt_id); }; field->m_parent = parent(); @@ -378,6 +378,15 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, on_change_OG(opt_key, get_value(opt_key)); } +void ConfigOptionsGroup::on_kill_focus(const std::string& opt_key) +{ + if (m_fill_empty_value) { + m_fill_empty_value(opt_key); + return; + } + reload_config(); +} + void ConfigOptionsGroup::reload_config() { for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { auto opt_id = it->first; diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index dd135636d..e80324628 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -85,7 +85,8 @@ public: size_t label_width {200}; wxSizer* sizer {nullptr}; column_t extra_column {nullptr}; - t_change m_on_change {nullptr}; + t_change m_on_change { nullptr }; + t_kill_focus m_fill_empty_value { nullptr }; std::function m_get_initial_config{ nullptr }; std::function m_get_sys_config{ nullptr }; std::function have_sys_config{ nullptr }; @@ -94,8 +95,6 @@ public: wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; int sidetext_width{ -1 }; - bool process_enter { false }; - /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent /// but defining it as const means a lot of const_casts to deal with wx functions. @@ -154,11 +153,6 @@ public: m_show_modified_btns = show; } - // The controls inside this option group will generate the event wxEVT_TEXT_ENTER - void set_process_enter() { - process_enter = true; - } - OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, column_t extra_clmn = nullptr) : m_parent(_parent), title(title), @@ -215,7 +209,7 @@ protected: const t_field& build_field(const Option& opt, wxStaticText* label = nullptr); void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field); - virtual void on_kill_focus () {}; + virtual void on_kill_focus(const std::string& opt_key) {}; virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value); virtual void back_to_initial_value(const std::string& opt_key) {} virtual void back_to_sys_value(const std::string& opt_key) {} @@ -251,7 +245,7 @@ public: void back_to_initial_value(const std::string& opt_key) override; void back_to_sys_value(const std::string& opt_key) override; void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key); - void on_kill_focus() override{ reload_config();} + void on_kill_focus(const std::string& opt_key) override;// { reload_config(); } void reload_config(); // return value shows visibility : false => all options are hidden void Hide(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 9a5c960bb..fa39ac240 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -270,7 +270,7 @@ Slic3r::GUI::PageShp Tab::add_options_page(const wxString& title, const std::str auto panel = this; #endif PageShp page(new Page(panel, title, icon_idx)); - page->SetScrollbars(1, 1, 1, 1); + page->SetScrollbars(1, 1, 1, 2); page->Hide(); m_hsizer->Add(page.get(), 1, wxEXPAND | wxLEFT, 5); From e1cea03cda35418603c948f982e300b2ec9c7d1a Mon Sep 17 00:00:00 2001 From: YuSanka Date: Tue, 11 Dec 2018 13:57:50 +0100 Subject: [PATCH 19/61] Added template for the handle of the TextCtrl's focus event --- src/slic3r/GUI/Field.cpp | 11 +++++++++++ src/slic3r/GUI/Field.hpp | 5 +++++ src/slic3r/GUI/GLCanvas3D.hpp | 2 ++ src/slic3r/GUI/GUI_ObjectManipulation.cpp | 5 +++++ src/slic3r/GUI/OptionsGroup.cpp | 11 +++++++++++ src/slic3r/GUI/OptionsGroup.hpp | 2 ++ 6 files changed, 36 insertions(+) diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 29218eaf6..ebc7f3665 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -77,6 +77,15 @@ void Field::on_kill_focus(wxEvent& event) m_on_kill_focus(m_opt_id); } +void Field::on_set_focus(wxEvent& event) +{ + // to allow the default behavior + event.Skip(); + // call the registered function if it is available + if (m_on_set_focus!=nullptr) + m_on_set_focus(m_opt_id); +} + void Field::on_change_field() { // std::cerr << "calling Field::_on_change \n"; @@ -220,6 +229,8 @@ void TextCtrl::BUILD() { auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style); temp->SetToolTip(get_tooltip_text(text_value)); + + temp->Bind(wxEVT_SET_FOCUS, ([this](wxEvent& e) { on_set_focus(e); }), temp->GetId()); temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event) { diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 66a71b34d..0097d3ec0 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -76,6 +76,8 @@ protected: //! in another case we can't unfocused control at all void on_kill_focus(wxEvent& event); /// Call the attached on_change method. + void on_set_focus(wxEvent& event); + /// Call the attached on_change method. void on_change_field(); /// Call the attached m_back_to_initial_value method. void on_back_to_initial_value(); @@ -89,6 +91,9 @@ public: /// Function object to store callback passed in from owning object. t_kill_focus m_on_kill_focus {nullptr}; + /// Function object to store callback passed in from owning object. + t_kill_focus m_on_set_focus {nullptr}; + /// Function object to store callback passed in from owning object. t_change m_on_change {nullptr}; diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 3076e3c5d..fc284c595 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -971,6 +971,8 @@ public: void viewport_changed(); #endif // ENABLE_CONSTRAINED_CAMERA_TARGET + void handle_sidebar_focus_event(const std::string& opt_key) {} + private: bool _is_shown_on_screen() const; void _force_zoom_to_bed(); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d12aabf9e..d193a11a9 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -71,6 +71,11 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : m_og->set_value(opt_key, double_to_string(0.0)); }; + m_og->m_set_focus = [this](const std::string& opt_key) + { + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(opt_key); + }; + ConfigOptionDef def; // Objects(sub-objects) name diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 512bcb4ac..4701ae20b 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -78,6 +78,11 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co if (!m_disabled) this->on_kill_focus(opt_id); }; + field->m_on_set_focus = [this](const std::string& opt_id) { + //! This function will be called from Field. + if (!m_disabled) + this->on_set_focus(opt_id); + }; field->m_parent = parent(); //! Label to change background color, when option is modified @@ -277,6 +282,12 @@ Line OptionsGroup::create_single_option_line(const Option& option) const { return retval; } +void OptionsGroup::on_set_focus(const std::string& opt_key) +{ + if (m_set_focus != nullptr) + m_set_focus(opt_key); +} + void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) { if (m_on_change != nullptr) m_on_change(opt_id, value); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index e80324628..9097dcab6 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -87,6 +87,7 @@ public: column_t extra_column {nullptr}; t_change m_on_change { nullptr }; t_kill_focus m_fill_empty_value { nullptr }; + t_kill_focus m_set_focus { nullptr }; std::function m_get_initial_config{ nullptr }; std::function m_get_sys_config{ nullptr }; std::function have_sys_config{ nullptr }; @@ -210,6 +211,7 @@ protected: void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field); virtual void on_kill_focus(const std::string& opt_key) {}; + virtual void on_set_focus(const std::string& opt_key); virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value); virtual void back_to_initial_value(const std::string& opt_key) {} virtual void back_to_sys_value(const std::string& opt_key) {} From 2ed77aadde48e3f896ea8f5267ec601e837b82cf Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Dec 2018 15:19:37 +0100 Subject: [PATCH 20/61] Korean translations by @ulsanether WIP: New icons by Memory WIP: Bicubic interpolation for SLA luminosity correction. --- resources/icons/move_hover.png | Bin 0 -> 2081 bytes resources/icons/move_off.png | Bin 0 -> 2073 bytes resources/icons/move_on.png | Bin 0 -> 1917 bytes resources/icons/overlay/move_hover.png | Bin 1977 -> 2081 bytes resources/icons/overlay/move_off.png | Bin 1862 -> 2073 bytes resources/icons/overlay/move_on.png | Bin 1868 -> 1917 bytes resources/icons/overlay/scale_hover.png | Bin 2185 -> 2459 bytes resources/icons/overlay/scale_off.png | Bin 1968 -> 2452 bytes resources/icons/overlay/scale_on.png | Bin 1970 -> 2449 bytes resources/icons/scale_hover.png | Bin 0 -> 2459 bytes resources/icons/scale_off.png | Bin 0 -> 2452 bytes resources/icons/scale_on.png | Bin 0 -> 2449 bytes resources/icons/toolbar.png | Bin 32101 -> 15742 bytes resources/localization/ko_KR/Slic3rPE.mo | Bin 0 -> 142724 bytes resources/localization/ko_KR/Slic3rPE.po | 4679 ++++++++++++++++++++++ src/libslic3r/Rasterizer/bicubic.h | 186 + 16 files changed, 4865 insertions(+) create mode 100644 resources/icons/move_hover.png create mode 100644 resources/icons/move_off.png create mode 100644 resources/icons/move_on.png create mode 100644 resources/icons/scale_hover.png create mode 100644 resources/icons/scale_off.png create mode 100644 resources/icons/scale_on.png create mode 100644 resources/localization/ko_KR/Slic3rPE.mo create mode 100644 resources/localization/ko_KR/Slic3rPE.po create mode 100644 src/libslic3r/Rasterizer/bicubic.h diff --git a/resources/icons/move_hover.png b/resources/icons/move_hover.png new file mode 100644 index 0000000000000000000000000000000000000000..8cd66cab912066852721cce6e3a7fb9bbf3c47c1 GIT binary patch literal 2081 zcmaJ?dsGu=79S9zP|)J8-D;JN0ThH}l1T`ekbr>@2$jgIfr1E=%mf1DK{AAxmgS*B zU98o%AgiFUR2ScC7o?vqg5dTKPv@MO@B8lD$M4?z zyXV^zEe&^dUgHb^fU6`z6icp~ZSOJ)`8}n|86=n0L|7sbr&SVps2&G|m{yL15;dBR z$KoiqBc~nb1Hh6JReU0m7`26`(5mUEO^0q$>qu(=;0K#@s3Hp|KslbS(g>&zzr9Qa zRhWR9;2*_^(uLv~stB_lk26c-73M4jf>DEmK)#7bI#A;T3Yyf}8UxQHpw9c{k$amM zqJr}(M3#X15>#STG#IMY{$Kz?XL49<1n~o529pahxDb;?V{&+G1`me8 zg@;N;(_>0rtVp~Ni#!Ra83du@K~P>^9zBmm*Xq+DCW0Uk1BPIjMrzOuJ2V7pqG=2s ziylO{L7`Xa2$fa?+B~9iZ7w08l1N{spw>l2y>hHEEEI|?8Dv6r5R=Y;)M{H^^V$X? z7XLqt*J>N$cj#~^7B^^f^$M~dN{>ZjvUa~8$fii5;ceBc$fls#BCR4SL#1shf3a+d$1pV!%o!U7PnC`>43hp?E; zFs_gx5{pBH9GHoSU>JcHZ6z85foc@^Vr&%|d%+g@U0YtL9!CkSK3=QMUSvUZhL+G8 zGPF7{G}P901oVzV6)KHQ=woZt%e+OnUS-5Fv0keN=PS)qy#@ta0i(W{^QY=XAahGFLo3@`}Ab?ZwTfC2BlLvA#;@UHqASER|oB0k&JF4=$LRz+!*VVt|_Jdk)4 zEPwariGs&}E9C?l-+4MsDV*5-D6odweBjsn9)HR=*wdTT4{I{}HC18{PP$ zxv)RwiF-#6CdkbnYgXlM|FiH!!@%8cC@57>AlsTUVGe(G&Ao7YfAd&9X7m9_Lx3Lq-GS|G zVVlf5Kzo(KUva8t;&l9`gr}KZ4~R9^C%b;->>hi6(q(e`YtNh(eCOH5r`31bd;py~ zZJViUCf?F^Y-FyN>g9#|EVn*sm+o#uT!F~2BiG9x_8nSN-uUpr(eTvL*2yyk_S2Jt zUwEnVX+gJ(8qs}Q_~Ofh0JnhwT`THF&%b}ay`#bJ)9fMQMZzC^cJ%;2#NE2(0X_dp zc`|niyzXXrJldUJ=XAHJ!8kCd3Al05sRRysZZtA}a9Di-aIYF-_uMO^tXCZy|0rhN z57MFT?tnDL+vbltEwP@5)*oLtLJyD5_&#pwH%l|e^p$C8(%Cu7Xuc$An!gT6Y!cUW zc0LwVx*e7!{CzAK=(}{dX5SI$c*liDt{S;paw6%g^^}dJ#UfcKsI5^EuNSzS?w<^VX3`%35v8 z#^hVros|lMd~kTQt@-VK#r}|pF+exve697+_{yS=W8-5c_xSLX;5jA_NI=fm_56dk z!Ie`nT9SOz{CVArzj)>yV3m*d4o-*s?389_IlH-b-$ct&^$H-JKWM^|8*{?PX3bOWE)4ou8_R!?|C+dyqKN4Z0`c}o*r5zGe&vaE^SGeRCF#Rt@wYf C+CB{c literal 0 HcmV?d00001 diff --git a/resources/icons/move_off.png b/resources/icons/move_off.png new file mode 100644 index 0000000000000000000000000000000000000000..9e44690df021843de34e9b9cc75702f59e478f5d GIT binary patch literal 2073 zcmaJ?dsGu=79Ru^D6F(1MRDyk6v1@!m|+N+2*@MJf)a|6mmqE@^MDA+gk+GQiy{cv zYKsLHvFqa;MPwDD1y=-llprca7ZsJ_+twOA7<^HMD!LN|+dn*=b7sEpyK{f{{_bn$ zi`baIyE@Nu1^~b{DpIOs*HGK-IE8)gQ)gXgmzj(#iCIEZOb(_efiQwrkzkYtOCyye zM&xF7lR*GrU#yNxVv-cm5}elXF`EwGsL`=#00>%O)M0oA$$%;{O|1>)JUG_Q0o6n> zCow=FROlkebamt^J-K96OdP%{14jwYf)FssC}9m~NCpFqnoO-hVhrZIwku)xHnD&M zzE)u}f;n$UB`IRT2wG2q0ele;hmZg;5amN6e*{JU2Esx}ED(wXkUtL+Nf4m~hQaZR z!+O&bltd|&kNaXz!JKr4(Mbe?oSYnfjz6E)rwJewMFm1w0K+_1gJ;OqGMJI4HF!^0 zkdg*muhubYS_|4NVk$bD3Ffd&--MviDHLxFYYpRxVpAqCVmblD7YZ~QTU@WT4UCff zoyI%04RN_TQlKOabhaL6>p^)>fZ5#rxg(n*%SQ5CA~Ph)t&%+K^7`z=#N2 z)ltw-f#GVcO*qe1s5fy-Nxga{NyzoI27H}qiTWKZ5FEy!K!WWVf+TqeBEonWLWd%1YQsbQ6RmFd-sPf)evEObqiN6+-gF2t@Ko0!RFb09=HL zD2{-oD6lopADU@G#CE7{`*t+g!`tB^wd_Rc+0jWf-mlgHBUEL-a0Qa2o);8BAd((Y<@+En1u9J$cUFK{VaujRHlE$ZG?e@^yg z_zCW!v~T}~d{?8+5L?z%EwDCKRatB2@A&yh!-Jv@)Uvw$N!yJEL)){w9g5J!aWiL_ zJ>*44-v=B42VfFlm(PAn9+n1Jd_8Xd+&g>yQ19}!4*Y8$M+MhI+_db&Iv*c5m%wS_ zl88G_KhF03QnF=ExdlB5nTlM1i|C`=#hi}ef~-v$A3t)guvS#7Mbe?Id8u=JqTP#} zq7`zB8@)6CfHLd!QrYP>%~_i|N*aNyJ(Y7dxI1y~oq(o%w0u&msjPah?}l>AlfvHk z_-$p0K;|O1lRIK|uN_WH$x0p$Iep6&$Z6O!)q8!x!=m3hKQtKazRx8y_50#ly8Cmh z3Y(T$e1DRBU${JF=K#9YiY~~$RmS!9&)67v9xLpVh=B1oi(kra9$`auO5fhxs*f2kIHFqs!xDwY+X_j>33y$hY&? z>i1Rl0J!wJC^^GxXZ_^g3O0M|Q%^NMm_C!cmcF-YYWci#gZt|% zU)hJ2bYC>IoO^M0Z%9|U}mH~%zzH!-KwMw)ryxiZosjFtx+HM}9bwigo7y~<>|8jjK`9;@c pF3^$Fni`*^z5K26NG18Pg}+;+V?z0CHrj*`VlP2ryx{2$GPJF5Ty literal 0 HcmV?d00001 diff --git a/resources/icons/move_on.png b/resources/icons/move_on.png new file mode 100644 index 0000000000000000000000000000000000000000..c19be461d4e1af97e88fff26c140dcc93fc7a51e GIT binary patch literal 1917 zcmaJ?c~BE)6#ozjh*CsE3&GA3tOvSDcC!iDU^zjw`U z&?P7M`G)uc0Q?dct5dl>#{K&6xV6@t*TZeWY`mULqfKl9VW)tKqzx35Xd$wxREi)A z^M0fj0pM9~PS>+~ZIZ%BTZDvLN9eHFI5Yr@q8&EExQb#S1C?#IDh2nxZ4p3bQYpxk zYQ?)<;DJi{H2gT8L3X%$Cuo1zeP$VuyWD*R=7ec5Qk&DD~5h8&RnF13lP!t+_1e`ZJ zX;P%BHDkWGn^KU&vNnZCR8UYLERYCkd$tI{aa<%uMJNh$8Zc96WeEptWx~fTs42#1 zH``bRFh!G~rAd!ySc@bV7FILIWSg8h!#SkQ3t`e&?nmA5` z;A#}b(Q#~|m0=01ks9}H=6sJ~v6o^MadwJeX?r?N=Z<%QE{A4mCWp2`adGadB)Og;DGJ6g837X*CF2&{L?9?> z5~C8q7@mACgHJn^lW>vP#tA7ZH&J$ObaroRAK@l)bD~<6?zlRjkD@ztuU_~%a6&-Y zwzI%1piJ#En>Te7%$pJv8=Ew+?7^fFz?-pJuX~;MQO=Ul*x-tJ8M=wYpH;1+SK>vesgc@4Er-7_XHR~)Fhp)SSqW5}KJNxqnd%Sk{HvZ)an%`@+{!*+@ zxz&>HSrY#mubbvQx*gq_2!m=D95O0C3-y2D58A3`w4f8;j5zrI{0xwlA|WpSgZoB0 z=SV?GXJaA1WD=`t@j_nXUZ3x9(w%n#PRi+_irCrx~6~a zGS^ofj{8SOt&<1C2SL_glrpUI;k}y!eM6eQPw01k(|-V*%|G*`V3Jpf=FKK+$`!J! z;Bq-K4gArt?U(BbfVs$a2iAhD>!BYS*fa_EA8!i?^yc{ znX|6dmVRvCtCCyaI)@vboBRWV)Z)^Nfd`Q*9eI78U{tdDk>B0rY5RQg1X-shAE=F> z7XIzmI=1%=I;6d~ME|6N4D8oZ$yihe8eHa=99zBAAd z%v1-jD%keJ?ZY?z%sBel)~h$Py*@S%cKNL)#g&a)FYwy3hh$3vyXMT&ROC(A|4o3t zVS0RpB;(zZum!jN4r^VBG$+;Y$@Y^!!AV6G`R&2p;!@uz-|he2xjX^ddNRRx=32uU zxvN+ZuBw$a6=qaz8QHa2x98ln{3!q7gzfdyf+m%Rmd&Z@f6T08O6$-~)}sw7zmn%B fqV`<02M@GHeSfTI>Va$SA7WyBvieAD*1G=y;S}V< literal 0 HcmV?d00001 diff --git a/resources/icons/overlay/move_hover.png b/resources/icons/overlay/move_hover.png index 6e91a7319bedb6f2f84ecae09b1888177d75e8f2..8cd66cab912066852721cce6e3a7fb9bbf3c47c1 100644 GIT binary patch delta 1267 zcmVWo0ciG&W%^ zVKiknEnze{W??ZhWM(ijGm}IEDk3suHe@+uW;iWjVK_1^F=8}jEjTnWWi4f7V>B~l zHDfkmlaK>Me+9bZqW}N|F-b&0RCwC#TVF_1Q5@cK%YWOAOW+?1(nUvP)% z?%bXCZtmXmgDdym^E>yP^X=aA{k{(ro3*Q4A_0d4e}59{y3Yu4@$MhLYJo`LK>#2C z5C8}O1ONg+h~jy1Vkr(m0#La@k)=*rn1ci7f&yS|oFwLGj;x5f#yRc@27vm_i;H3; zV$Xtcj(dUtpn9VsJ9mk6#w(9|XyY{i>*A%dBTCtJx^}c7hqnOSD2(3`EfGi4wWAF= zyab>|f33*LNsy+`mWP~k>Npjz09cxJ<bg8xm9Zc_-)pYfe`K{z%_VA(01##XVAEO_8xhIXWbtga zH%3+f0B5)EPTK1(b}BTfQOR5ZfM;V2jKx-+FlPWzwLL3N)CNLzfd#I~MSNdwx>2w;p_Jsa6 ze}(8(?F{YkvLtcEWrMA``kmFtDFAS7U|e|9^HiY7q2u}4A|)kdDGs{!$xemHISl|x zMV`!+@7kIgmAUWfpGRMZ#_gEk4wNxnYh>JVw9X064cWFT;V?VXsx@ z$;-Dak8Iy`|5pU>ch-A>=v*Ee7=7-s0!T#2DLSkU~i40C4UK!^WEG z5`oL1>gaeLR7YJu`7t^GS2?T!f7r7+CJi>0=Z0I(dXu#OiX?ZDGnMDr-ta>5319)B ztw5-b>%|9c4*+K~DVC6nKD^lkQ76ZJq_=oQaJUR+S)Kz+;4UO`Y>r3-}6uom-*?2>=8D0ssMk06+lXB*a4h d_xZm93;@}y-w0NAO(*~W002ovPDHLkV1j2kJ3#;d delta 1162 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 UvU(7v+W-In07*qoM6N<$f>8zGzW@LL delta 1052 zcmV+%1mpYt4$Ka)UIHF4HZU_pLNY={Loh=zF-15yGe$&3LoqT!G(s{oMw5I39v(0@ zFf&9#GC@T{FhemhML0M!MnpwJF)~6lLNYW)lehwCBr#<;GB-FhVl6W=WHl``F*ai@ zI5A=~EnzleG-WenWi~c6HIqaGDk3v6GB!0fWHBvfIbmfjWjQoAEn+u0W-Vf5IWuKt zVKg{5laK>Me<5O)G5`PrTuDShRCwC#TF+|}K@{E%1qBZkX+TI%;wiZ_H*dDr3MRB^ zz=IwFmJ-tH&3{8_DWSq1DpcqXaRqy|dg`rSl9PC9L1-!UAm}N64_`_QneFUmX0t!$ z!7$10?96-L?!5QCw`+Gg9V^X}u~IGu00V#lzyM$Xe=q=~SjJ=q*|x1^@!wB#Z>Yci z`sA^W`DI9+Ej36305E<5-8TQN0)(B^0085&=-Ox#eCP}<4-ihn24GY^V7FytJqfUWQ&sav4wVZ6oXnu**3xce09N)kg$ z82cnqe_H?s<@nJbm%QXBZ-tYjY=I3Q##=mgBN1BwcmE-}*#MW$^0lL@nSav?;;4uV|k|7v4ENngman7i5tQ zfbiBY6&~|>*Rx!>sEEdhNRFgTd4NN6du5SZ&~FZX-A`pHm3`MB{RD8`K99B=VdF`+yq|Xr zzM=qo*9EUINGUC4bvnWz=AJ|+9R{`WfGdd53=|Qjw2tGvME6W^`c zaH^`aj74*ou-*q%6DlolbKRxzJ*DypV?Eq-63Ui60~VRM_#ruqiE)C=6IOg&vOMgYe&NXY zfIffl-nzPo>caT4iuI!Sg~$?TZX!A7G0EfsoIovm3~8wPJ}9z82nKEmiYmK7^7*(j z!MgQ-dF_mkrGpA{WvSQTy6Ue)BN+t-M1^>KEN!wu1Aqa*0AK(x02lxa07;a;0t^5F WH%ZfWx0S;H0000G%+nSH8?gc zVmL5kEjVLjG-EPmHf1w4VUt7yDkL{JFg7@2H8(9XIbvlkW@a=tEjeT|H!Ws0G&MLf zGGbz4H8qox144gQI^D|v00pZ_L_t(|+U#3-OdM4h-9&G_>Jp{}3w`>4N2u zqukqN&zT+HM_oX&hGPe^F!Lpk{rH%f_j|v0{N9`Wz{Y=G31Wi&P{oTp0I7aUZekIFyBPxYku1 zo~?Ef2P}XIu&rnxupIj(OP%#th&To9o|xR)v2SYoEF%!F+Ufh~GZ9i9_%Ak0lz)HQ z)DQmgH1B`p{ju_F@DDamRey2Qs0aU}?aD20Z&f@6{-?j*JN5o`!%gsC-{Q6TNUEv; z{O|slKL6^~$yV_5qhui!I-i%o|Eb&DPCFlHT?W&Fd zwG>yvCsY{fMaZ3T&A)N5a0M~zhsX()(+^V!Rr7!1N>~1ZYgDeDIlnHzfX%>}!?vm; z;J~%Xi=`4eo`M}=~GD=Z-}yemKGjrP5PB_Ag%<2}9cfDy0) zX29yG*m9)Q5b6{{^ z@S#Hqidau?JYWQ@fSENF@FsC3feDD+T1lLz695=@~vCxe5q~6@- z=>z~qzzUcFyW1*Yb1;mydg$x*GXIbNZq%bv%BuiR080drIELLR2r8a3;6EEJOZ@iMboWXtBS{h~D=SM)PfvePDwT@aZj9M%wl+34b`%vA zHQQ`9h7|%P(UEe}<}fHkxcpp?rQIEXb#-+)85tRgtJ$NGk&&CUTCHDZX69K|2tbnJ z2&o!8hE7+w!N$-l1r`$%BSSr(xE6oITBp-(LquHa>gqDPy#_cBMD8>mEG{liiDB59 z%xkD8K0aQ>s{k7Wz@egjZ{YI-`!uf=M9D%qM}S&Fh$59jt*;pODSk+v%sB!KHV0*< z7&Cki&@f=V&AEMmiDDd4KMWm3Co7SDiZXGI0I*SXsv2Fw$%;P2J`*WSaqMbKzx=%kK^~6QvykE zPO8AJO_Ci+{^Y|)QdGK?Rz$;6x`swkl!FxlEHneiXTbMO(-SKN)YQ~8jzCds(Re$u z=H})JRtSLC;dvLw%1WB1s-L@jKRq=_Mv!N-)mi6usk1dQV)h#z$mM@Mji$?m-Rhjp zDxqxtb4Er+>=hLib!$ODd3kv~)W@0%oawO)qmN+}w_2@KO-)T_Wo2bAt1`IIdX4nd ztbdQ1=hFtKKVX`>X1-DZxa9y`ZCg^RtSfV-voFlTfV5fy6JYD%9U`R4u6P*!U;4>n w`MLZ?h$nz2fG2<_fG5Cn4CDI0&;KL90L{ZXoO=H70RR9107*qoM6N<$g2}A&@Bjb+ delta 1374 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 delta 1155 zcmV-}1bq9H6R;1kUIHF8FhfH#LP9Y`Loh=zF-15yGe$&3LoqT!G(s{oMw5I39v(C> zLqjt{LNG-`FhemhML0M!MnpwJF)~6lLNYW)lehwCBsXF>HDNF`F)cVZV=*l>I5{{i zI59Y3EoM1oF=S?DV>M!7IFm#JDkLy6He+EiFgYz@IAUZiFf=qdEjTeSH7#T@WnnjC zF<~`1VmFhL144hRZuQdu00Y2DL_t(|+U#3RNK{c2exuosVw?OSMvbCuArVAGEuu}E zgcvAnQ_`Y{2#prDh@^sS;Ub6%5n4&RRy~Lyh=`Jwi3=fu!mOw?gJ%32hjIEo-x;_* zW_aVgHMXkZ_5u9*byBxI1OuR5bgdKJ zeft1<>bK&A9)`dG)bRlDU3B;F1Hw$D$OH6Ch2cypB-;uFQCI35hylctjstPKLcaK}G>)Uh9F55Db70(Y3jdC{Tz2m}idxe_ff}TBRY;bxd?OQNTn2V*nqSAUtU$ ziV=TVxt+oScD zQ^7$oVkzk`unV{tp)dvK-DIsK+Rn_Ud0jJ3{FA5{jfxrJ`l( zyd~{05%S!?F;Sq9g%FBYV$n{FhIhK<0o;GzTjP4{N^RbJ~7~R$l@|xc$fFK;?h5Wbru2?L_J;nQFFhI9$+shZa zf;tx%MKP*~E&-Q?|7kflI7w$L=O$w*DdAo|L#YQa0&|JuKq;==aD+WWJHvs7!5lQ9 z@Lv@ETb??q5vbg-wMMAu3lbhz-7DNz+W!}M_Rsv^pyp%@U<_akU<_akpfmjwU;sv- VKg5sH9*6({002ovPDHLkV1h~A^k@J8 diff --git a/resources/icons/overlay/scale_on.png b/resources/icons/overlay/scale_on.png index ce2f5bc23d20c906131e0a072c0d2bb34b58c017..4d4c075f385ccd68cf5ec27253e2762851fba7a3 100644 GIT binary patch delta 1638 zcmV-s2ATP?50MkFUjiLLI5{##MMg$6LNzflML0n)L`FtKK{hfmIYcrtlY9an9YHub zGDby3Ml?b-F)>9rK`=x{Mnge1GBG(sGBT660%#;;VKX^mG&wOXHDWX|Ei^SaHZ5W} zFk>w^V`MaAGG;bqGc{q8L<1@$H#jghIAb+8EiyS`Wi4iAG&U_cWHL7`W;HZ5I5IL~ zVq!Hllad2Me-~>|n*aa>p-DtRRCwC#T6=6$RT#go-agl^+q!k{!CZj>i-3YS_Ap}+ z4HJUIM34v}BrGumqRTWUXpDcT{GlNxpmQw22MD@kLWFE8!!4`~2xHD!7~`=Q%x&#@ zZF_rrU-lc8&1Q!6F;cwuYi|0>*L&{yonOyA-*?abe^9g&eHW3iVTi;{7C;t27C;t2 z7C;stlm;S#BzrJY??dt6-;!udVenx@us^|zDdL&w{cr^P;oRx`aBb)#*biKQ6L1UF zh)^3e{E)b2b)RyDzb|n^h5f+k9PbGDzdUU$+1R5l1-!l7QTxq&dI0eLGlu0my0p&$ zu3klbf4zT+>pI}~+VqR7F6uS_-uHy7e(y@^H^93t>mK{CRlgZ<%_8@yUC%h%0dKOY zGisZR+W~)lU-<2Va=%`KIw{zO70iXiAuStv4^kEr|8O14rNroTC;63=F2nMFd}aQl%s$g1!Muj7l7nZ^yZYbh1x&Jo-9=J-ve^a2@ zjVA+s#im($u)uvI+|EdpL=UDPe5y!WG+&LXmC+syEz(SjyT!J1aHrKL`oyRRFs>3f zr&6GFK1_RZj`#OS2sX}XTwAOyUOJoqXfBzXl}%rjn(*Jhh0Q*r_%(YPYlNn{(nn6a``yIgVtP)dQL36X~P0L>XpkL2{#kxx?Hxaw>MW0zzUxMt)$aL7?WiUL5{ zNmG5&3s=5M-cr`DOp;&}z{pjDaos<>jMiV85|Ki zO{}fm?7RAQA$1uFBh7A{JaQ_be6vlHcP7)nEZk&MuLgX~LjUsWvyK+P`&tZ3U+d6T z06vxDJ-4C4-W1$+n{iv#di{C}iIPOL2hNBL*FZH~t8>)#MKuftTYntq4B);v{yx4P z$58=1q!XOewFPpaND!QD3%fufP|hA)g#l%?u#5Dl>>-9d4gahBWG3~wav~%PAPXQ1 kAPXQ1APW!^_O}270Ca9vP+DwDGynhq07*qoM6N<$f-lSLo&W#< delta 1155 zcmV-}1bq9E6S5DmUjiLMMMFYGLo!7}FhemhML0M!MnpwJF)~6lLNYW)lY9an9YRGz zLPbL{MME${F)>9rI5S2>MME(%LNr1$G)9xS0%#;RVmLKnFf=hOI5%T4Ei^beI4w9a zIAJYjIb|_qW@ckGVqrLwL<1@$Ffuk{VKFc{EnzrfWGyf>G&wCeF)%ePWHDu7H)Anj zH92B8lad2Mf12ag$N&HX!bwCyRCwC#TTMt*Q51foS*Fw`e~3||C|gLhiKs=iX_F8^ zVVja*`XRyz7qy6_f@zF1A5FebfgE&IzVs*Hts=iA ziu?ecDe_)mlYF5;Mc$_<+^5J{XQw>VtH}Azd%#v{YniA+56}wSUTtSWZgYe_K;~o( z=GRmZ92@lA!f8RD!ymXZzuyMRl6?g!9UR5|wmSNZkpQT*a7%i;WidW?c8xBeBhBZQ7GqL}xnRJcr? zx1=2=LarM)CJGd?5JK@rEZT|D@Ikjce}H@ahk+eHRL4Y!ha7(Z`hl0)8302*ZP|PE zjo_bX=L5tDOD8yHEue7}UxOxo;W2ra)Rz{<8;#`0QMnI?q$#FNIVr z^ju)CWnWNy#k;up%}b1T#eg>;OUQKLUEVvEeaW#0^55#aVzC(a6d#ts0MSS!vUH)# zuXBN66vK+>GH^xspO$@tlXS+iWp9#_lFsEblzIdsFqb$6l;X+_N7xIrGvsR+%t2!c z|5f3?=c%I_fyxb8YlMovAmMV=y~25={eO{b|IGglYEH%g#sJ0u#sJ0uI@3P^1^|if VM}ui&heH4W002ovPDHLkV1is@08;<} diff --git a/resources/icons/scale_hover.png b/resources/icons/scale_hover.png new file mode 100644 index 0000000000000000000000000000000000000000..394cb4b20dda6b9e8f664b68334fef8eeffd25f9 GIT binary patch literal 2459 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 literal 0 HcmV?d00001 diff --git a/resources/icons/toolbar.png b/resources/icons/toolbar.png index d078d4656bd0782bd75098bfc7ac2852f98ed4c6..75faea658e47d9c645d244696849410014bea213 100644 GIT binary patch literal 15742 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 Pzmk!Vznvwn>+`<=jrRyy diff --git a/resources/localization/ko_KR/Slic3rPE.mo b/resources/localization/ko_KR/Slic3rPE.mo new file mode 100644 index 0000000000000000000000000000000000000000..10164d14a31a32464c48c43a2df30fc34aae5f3d GIT binary patch literal 142724 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\n" +"Language-Team: Korean\n" +"Language: ko_KR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.2\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Crowdin-Project: slic3rkorean\n" +"X-Crowdin-Language: ko\n" +"X-Crowdin-File: ko_KR.po\n" + +#: xs/src/slic3r/GUI/AboutDialog.cpp:32 +msgid "About Slic3r" +msgstr "Slic3r에 대하여" + +#: xs/src/slic3r/GUI/AboutDialog.cpp:67 +msgid "Version" +msgstr "버전" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:39 +msgid "Shape" +msgstr "모양" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:46 +msgid "Rectangular" +msgstr "직사각형" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:50 xs/src/slic3r/GUI/Tab.cpp:1826 +#: lib/Slic3r/GUI/Plater.pm:498 +msgid "Size" +msgstr "사이즈" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:51 +msgid "Size in X and Y of the rectangular plate." +msgstr "사격형 플레이트 X 및 Y 크기" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:57 +msgid "Origin" +msgstr "원본" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:58 +msgid "Distance of the 0,0 G-code coordinate from the front left corner of the rectangle." +msgstr "사각형의 전면 왼쪽된 모서리에서 0, 0 G-코드 좌표 거리입니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:62 +msgid "Circular" +msgstr "원형" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:65 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 xs/src/slic3r/GUI/ConfigWizard.cpp:446 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:460 xs/src/slic3r/GUI/RammingChart.cpp:81 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 +#: xs/src/libslic3r/PrintConfig.cpp:133 xs/src/libslic3r/PrintConfig.cpp:181 +#: xs/src/libslic3r/PrintConfig.cpp:189 xs/src/libslic3r/PrintConfig.cpp:237 +#: xs/src/libslic3r/PrintConfig.cpp:248 xs/src/libslic3r/PrintConfig.cpp:363 +#: xs/src/libslic3r/PrintConfig.cpp:374 xs/src/libslic3r/PrintConfig.cpp:393 +#: xs/src/libslic3r/PrintConfig.cpp:531 xs/src/libslic3r/PrintConfig.cpp:890 +#: xs/src/libslic3r/PrintConfig.cpp:1002 xs/src/libslic3r/PrintConfig.cpp:1010 +#: xs/src/libslic3r/PrintConfig.cpp:1068 xs/src/libslic3r/PrintConfig.cpp:1086 +#: xs/src/libslic3r/PrintConfig.cpp:1104 xs/src/libslic3r/PrintConfig.cpp:1166 +#: xs/src/libslic3r/PrintConfig.cpp:1176 xs/src/libslic3r/PrintConfig.cpp:1292 +#: xs/src/libslic3r/PrintConfig.cpp:1300 xs/src/libslic3r/PrintConfig.cpp:1342 +#: xs/src/libslic3r/PrintConfig.cpp:1351 xs/src/libslic3r/PrintConfig.cpp:1361 +#: xs/src/libslic3r/PrintConfig.cpp:1369 xs/src/libslic3r/PrintConfig.cpp:1377 +#: xs/src/libslic3r/PrintConfig.cpp:1463 xs/src/libslic3r/PrintConfig.cpp:1669 +#: xs/src/libslic3r/PrintConfig.cpp:1739 xs/src/libslic3r/PrintConfig.cpp:1773 +#: xs/src/libslic3r/PrintConfig.cpp:1969 xs/src/libslic3r/PrintConfig.cpp:1976 +#: xs/src/libslic3r/PrintConfig.cpp:1983 xs/src/libslic3r/PrintConfig.cpp:2015 +#: xs/src/libslic3r/PrintConfig.cpp:2025 xs/src/libslic3r/PrintConfig.cpp:2035 +msgid "mm" +msgstr "mm" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:66 xs/src/libslic3r/PrintConfig.cpp:528 +msgid "Diameter" +msgstr "노즐 직경:" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:67 +msgid "Diameter of the print bed. It is assumed that origin (0,0) is located in the center." +msgstr "인쇄 침대의 직경. 원점 (0,0) 은 중심에 있다고 가정합니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:71 +#: xs/src/libslic3r/GCode/PreviewData.cpp:175 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:102 +msgid "Custom" +msgstr "커스터" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:75 +msgid "Load shape from STL..." +msgstr "STL파일 로드." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:120 +msgid "Settings" +msgstr "설정" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:299 +msgid "Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "침대 모양 (STL/OBJ/AMF/3MF/PRUSA) 에서 가져오려는 파일을 선택 합니다:" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:316 +msgid "Error! " +msgstr "에러!" + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:325 +msgid "The selected file contains no geometry." +msgstr "선택한 파일에는 형상이 없는 포함 되어 있습니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.cpp:329 +msgid "The selected file contains several disjoint areas. This is not supported." +msgstr "선택한 파일 여러 분리 된 영역을 포함 되어 있습니다. 이 지원 되지 않습니다." + +#: xs/src/slic3r/GUI/BedShapeDialog.hpp:44 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 +msgid "Bed Shape" +msgstr "배드 모양" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:53 +msgid "Network lookup" +msgstr "네트워크 조회" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:66 +msgid "Address" +msgstr "주소" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:67 +msgid "Hostname" +msgstr "호스트이름" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:68 +msgid "Service name" +msgstr "서비스 이름" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:69 +msgid "OctoPrint version" +msgstr "옥토프린트 버전" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:187 +msgid "Searching for devices" +msgstr "디바이스 검색" + +#: xs/src/slic3r/GUI/BonjourDialog.cpp:194 +msgid "Finished" +msgstr "완료" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:13 +msgid "Buttons And Text Colors Description" +msgstr "버튼 및 텍스트 색상 설명" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:38 +msgid "Value is the same as the system value" +msgstr "값은 시스템 값과 같습니다" + +#: xs/src/slic3r/GUI/ButtonsDescription.cpp:55 +msgid "Value was changed and is not equal to the system value or the last saved preset" +msgstr "값이 변경 되었고 시스템 값 또는 마지막으로 저장 된 사전 설정과 같지 않음" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:15 +msgid "Upgrade" +msgstr "업그레이드" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:17 +msgid "Downgrade" +msgstr "다운그레이드" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:19 +msgid "Before roll back" +msgstr "롤백 전에" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:21 +msgid "User" +msgstr "사용자" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:24 +msgid "Unknown" +msgstr "알 수 없음" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:36 +msgid "Active: " +msgstr "활성:" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:42 +msgid "slic3r version" +msgstr "slic3r에 대하여" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:43 +msgid "print" +msgstr "프린트" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:44 +msgid "filaments" +msgstr "필라멘트" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:45 +msgid "printer" +msgstr "프린터" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 xs/src/slic3r/GUI/Tab.cpp:730 +msgid "vendor" +msgstr "벤더" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:49 +msgid "version" +msgstr "버전" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:50 +msgid "min slic3r version" +msgstr "최소 slic3r 버전" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:52 +msgid "max slic3r version" +msgstr "최대 slic3r 버전" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "model" +msgstr "모델" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:55 +msgid "variants" +msgstr "변종" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:67 +msgid "Incompatible with this Slic3r" +msgstr "이 Slic3r와 호환 되지 않음" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:70 +msgid "Activate" +msgstr "활성화" + +#: xs/src/slic3r/GUI/ConfigSnapshotDialog.cpp:96 xs/src/slic3r/GUI/GUI.cpp:349 +msgid "Configuration Snapshots" +msgstr "구성 스냅숏" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:88 +msgid "nozzle" +msgstr "노즐" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:89 +msgid "(default)" +msgstr "(기본값)" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:108 +msgid "Select all" +msgstr "모두 선택" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:109 +msgid "Select none" +msgstr "선택 없음" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 +#, c-format +msgid "Welcome to the Slic3r %s" +msgstr "Slic3r %s에 오신것을 환영 합니다" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:218 +msgid "Welcome" +msgstr "환영합니다" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:224 xs/src/slic3r/GUI/GUI.cpp:346 +#, c-format +msgid "Run %s" +msgstr "%s 실행" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:226 +#, c-format +msgid "Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print." +msgstr "안녕하세요, Slic3r prusa 버전에 오신 것을 환영 합니다! 이 %s 초기 구성;에 도움이 됩니다. 단지 몇 가지 설정 하 고 당신은 인쇄 준비가 될 것입니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:230 +msgid "Remove user profiles - install from scratch (a snapshot will be taken beforehand)" +msgstr "사용자 프로필 제거-처음부터 설치 (스냅숏은 사전에 수행 됩니다)" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:252 +msgid "Other vendors" +msgstr "다른 공급 업체" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:254 +msgid "Custom setup" +msgstr "사용자 지정 설치" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 +msgid "Automatic updates" +msgstr "자동 업데이트" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:278 +msgid "Updates" +msgstr "업데이트" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:286 xs/src/slic3r/GUI/Preferences.cpp:59 +msgid "Check for application updates" +msgstr "프로그램 업데이트 확인" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:289 xs/src/slic3r/GUI/Preferences.cpp:61 +msgid "If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done." +msgstr "활성화 된 경우 Slic3r은 Slic3r PE 온라인의 새 버전을 확인합니다. 새 버전을 사용할 수있게되면 다음 응용 프로그램 시작시 알림이 표시됩니다 (프로그램 사용 중에는 절대로 사용하지 마십시오). 이것은 알림 메커니즘 일뿐 자동 설치가 수행되지 않습니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:293 xs/src/slic3r/GUI/Preferences.cpp:67 +msgid "Update built-in Presets automatically" +msgstr "기존의 설정 자동 업데이트" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:296 xs/src/slic3r/GUI/Preferences.cpp:69 +msgid "If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup." +msgstr "활성화 된 경우 Slic3r은 백그라운드에서 내장 시스템 사전 설정의 업데이트를 다운로드합니다. 이러한 업데이트는 별도의 임시 위치에 다운로드됩니다. 새로운 사전 설정 버전을 사용할 수있게되면 응용 프로그램 시작시 제공됩니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:297 +msgid "Updates are never applied without user's consent and never overwrite user's customized settings." +msgstr "업데이트는 사용자의 동의없이 적용되지 않으며 사용자의 사용자 지정된 설정을 덮어 쓰지 않습니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:302 +msgid "Additionally a backup snapshot of the whole configuration is created before an update is applied." +msgstr "또한 업데이트가 적용되기 전에 전체 구성의 백업 스냅 샷이 생성됩니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:309 +msgid "Other Vendors" +msgstr "다른 공급 업체" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:311 +msgid "Pick another vendor supported by Slic3r PE:" +msgstr "Slic3r PE가 지원하는 다른 공급 업체를 선택하십시오:" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 +msgid "Firmware Type" +msgstr "펌웨어 타입" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:370 xs/src/slic3r/GUI/Tab.cpp:1606 +msgid "Firmware" +msgstr "펌웨어" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:374 +msgid "Choose the type of firmware used by your printer." +msgstr "프린터에 패치할 펌웨어를 선택하세요." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:409 +msgid "Bed Shape and Size" +msgstr "배드 모양과 크기" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:412 +msgid "Set the shape of your printer's bed." +msgstr "프린터 배드모양을 설정하세요." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 +msgid "Filament and Nozzle Diameters" +msgstr "필라멘트와 노즐 크기" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:426 +msgid "Print Diameters" +msgstr "인쇄 직경" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:442 +msgid "Enter the diameter of your printer's hot end nozzle." +msgstr "핫 엔드 노즐 직경을 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:445 +msgid "Nozzle Diameter:" +msgstr "노즐 직경:" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:455 +msgid "Enter the diameter of your filament." +msgstr "필라멘트의 직경을 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:456 +msgid "Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average." +msgstr "정밀도가 필요하므로 캘리퍼를 사용하여 필라멘트를 따라 여러 번 측정 한 다음 평균을 계산하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:459 +msgid "Filament Diameter:" +msgstr "필라멘트 직경:" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 +msgid "Extruder and Bed Temperatures" +msgstr "익스트루더와 배드 온도" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:477 +msgid "Temperatures" +msgstr "온도 " + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:493 +msgid "Enter the temperature needed for extruding your filament." +msgstr "필라멘트 압출에 필요한 온도를 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:494 +msgid "A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS." +msgstr "보통 PLA의 경우 160 ~ 230 ° C, ABS의 경우 215 ~ 250 ° C입니다." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:497 +msgid "Extrusion Temperature:" +msgstr "출력 온도 :" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:498 +#: xs/src/slic3r/GUI/ConfigWizard.cpp:512 +msgid "°C" +msgstr "°C" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:507 +msgid "Enter the bed temperature needed for getting your filament to stick to your heated bed." +msgstr "필라멘트가 핫배드에 접착하는데 필요한 배드온도를 입력하십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:508 +msgid "A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed." +msgstr "보통은 PLA의 경우 60 ° C이고 ABS의 경우 110 ° C입니다. 핫배드가 없는 경우에는 0으로 두십시오." + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:511 +msgid "Bed Temperature:" +msgstr "배드 온도 :" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:824 +msgid "< &Back" +msgstr "< &뒤로" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:825 +msgid "&Next >" +msgstr "&다음 >" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:826 +msgid "&Finish" +msgstr "&완료" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:896 +msgid "Configuration Wizard" +msgstr "구성 마법사" + +#: xs/src/slic3r/GUI/ConfigWizard.cpp:898 +msgid "Configuration Assistant" +msgstr "구성 도우미" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:87 +msgid "Flash!" +msgstr "완료!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:88 +msgid "Cancel" +msgstr "취소" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:128 +msgid "Flashing in progress. Please do not disconnect the printer!" +msgstr "아직 플래싱 중입니다. 커넥트를 분리하지 마십시오!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:155 +msgid "Flashing succeeded!" +msgstr "플래싱 성공!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:156 +msgid "Flashing failed. Please see the avrdude log below." +msgstr "플래시 실패. 아래의 로그를 확인하세요." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:157 +msgid "Flashing cancelled." +msgstr "깜빡임 취소됨." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:294 +msgid "Cancelling..." +msgstr "취소 중..." + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:347 +msgid "Firmware flasher" +msgstr "펌웨어 플래셔" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:367 +msgid "Serial port:" +msgstr "시리얼 포트:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:369 +msgid "Rescan" +msgstr "재검색" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:374 +msgid "Firmware image:" +msgstr "펌웨어 이미지:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:377 +msgid "Status:" +msgstr "지위:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:378 +msgid "Ready" +msgstr "준비" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:381 +msgid "Progress:" +msgstr "진행:" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:400 +msgid "Advanced: avrdude output log" +msgstr "고급: avrdude 로그 출력" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:446 +msgid "" +"Are you sure you want to cancel firmware flashing?\n" +"This could leave your printer in an unusable state!" +msgstr "" +"펌웨어 플래싱을 취소하시겠습니까?\n" +"프린터가 사용할 수 없는 상태가 될 수 있습니다!" + +#: xs/src/slic3r/GUI/FirmwareDialog.cpp:447 +msgid "Confirmation" +msgstr "확인" + +#: xs/src/slic3r/GUI/GLCanvas3D.cpp:2308 +msgid "Detected object outside print volume" +msgstr "출력물이 프린터 출력 사이즈를 넘었습니다" + +#: xs/src/slic3r/GUI/GUI.cpp:233 +msgid "Array of language names and identifiers should have the same size." +msgstr "언어 이름과 식별자 배열은 같은 크기 여야합니다." + +#: xs/src/slic3r/GUI/GUI.cpp:244 +msgid "Select the language" +msgstr "언어를 선택" + +#: xs/src/slic3r/GUI/GUI.cpp:244 +msgid "Language" +msgstr "언어" + +#: xs/src/slic3r/GUI/GUI.cpp:306 xs/src/libslic3r/PrintConfig.cpp:195 +msgid "Default" +msgstr "기본값" + +#: xs/src/slic3r/GUI/GUI.cpp:349 +msgid "Inspect / activate configuration snapshots" +msgstr "구성 스냅 샷 검사 / 활성화" + +#: xs/src/slic3r/GUI/GUI.cpp:350 +msgid "Take Configuration Snapshot" +msgstr "구성 스냅 샷 가져 오기" + +#: xs/src/slic3r/GUI/GUI.cpp:350 +msgid "Capture a configuration snapshot" +msgstr "구성 스냅 샷 캡처" + +#: xs/src/slic3r/GUI/GUI.cpp:353 xs/src/slic3r/GUI/Preferences.cpp:9 +msgid "Preferences" +msgstr "환경 설정" + +#: xs/src/slic3r/GUI/GUI.cpp:353 +msgid "Application preferences" +msgstr "응용 프로그램 환경 설정" + +#: xs/src/slic3r/GUI/GUI.cpp:354 +msgid "Change Application Language" +msgstr "응용 프로그램 언어 번경" + +#: xs/src/slic3r/GUI/GUI.cpp:356 +msgid "Flash printer firmware" +msgstr "프린터 펌웨어 플래시" + +#: xs/src/slic3r/GUI/GUI.cpp:356 +msgid "Upload a firmware image into an Arduino based printer" +msgstr "아두이노 기반의 프린터 이미지 업로드" + +#: xs/src/slic3r/GUI/GUI.cpp:368 +msgid "Taking configuration snapshot" +msgstr "구성 스냅 샷 만들기" + +#: xs/src/slic3r/GUI/GUI.cpp:368 +msgid "Snapshot name" +msgstr "스냅 샷 이름" + +#: xs/src/slic3r/GUI/GUI.cpp:406 +msgid "Application will be restarted" +msgstr "애플리케이션이 다시 시작됨" + +#: xs/src/slic3r/GUI/GUI.cpp:406 +msgid "Attention!" +msgstr "주목!" + +#: xs/src/slic3r/GUI/GUI.cpp:422 +msgid "&Configuration" +msgstr "&구성" + +#: xs/src/slic3r/GUI/GUI.cpp:446 +msgid "You have unsaved changes " +msgstr "저장되지 않은 변경 사항이 있습니다" + +#: xs/src/slic3r/GUI/GUI.cpp:446 +msgid ". Discard changes and continue anyway?" +msgstr ". 변경 사항을 취소하고 계속 하시겠습니까?" + +#: xs/src/slic3r/GUI/GUI.cpp:447 +msgid "Unsaved Presets" +msgstr "저장되지 않은 기존설정" + +#: xs/src/slic3r/GUI/GUI.cpp:655 +msgid "Notice" +msgstr "공지" + +#: xs/src/slic3r/GUI/GUI.cpp:660 +msgid "Attempt to free unreferenced scalar" +msgstr "참조 되지 않은 스칼라를 비우려고" + +#: xs/src/slic3r/GUI/GUI.cpp:662 xs/src/slic3r/GUI/WipeTowerDialog.cpp:39 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:321 +msgid "Warning" +msgstr "위험" + +#: xs/src/slic3r/GUI/GUI.cpp:859 +msgid "Support" +msgstr "서포트(지지대)" + +#: xs/src/slic3r/GUI/GUI.cpp:862 +msgid "Select what kind of support do you need" +msgstr "서포트의 종류를 선택하세요" + +#: xs/src/slic3r/GUI/GUI.cpp:863 xs/src/libslic3r/GCode/PreviewData.cpp:162 +msgid "None" +msgstr "없음" + +#: xs/src/slic3r/GUI/GUI.cpp:864 xs/src/libslic3r/PrintConfig.cpp:1656 +msgid "Support on build plate only" +msgstr "출력물만 서포트를 지지" + +#: xs/src/slic3r/GUI/GUI.cpp:865 +msgid "Everywhere" +msgstr "모든곳" + +#: xs/src/slic3r/GUI/GUI.cpp:877 xs/src/slic3r/GUI/Tab.cpp:844 +msgid "Brim" +msgstr "브림" + +#: xs/src/slic3r/GUI/GUI.cpp:879 +msgid "This flag enables the brim that will be printed around each object on the first layer." +msgstr "이 플래그는 첫 번째 레이어의 각 개체 주위에 인쇄 될 브림을 활성화합니다." + +#: xs/src/slic3r/GUI/GUI.cpp:888 +msgid "Purging volumes" +msgstr "볼륨 삭제" + +#: xs/src/slic3r/GUI/GUI.cpp:930 +msgid "Export print config" +msgstr "인쇄 설정 내보내기" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r error" +msgstr "Slic3r 오류" + +#: xs/src/slic3r/GUI/MsgDialog.cpp:64 +msgid "Slic3r has encountered an error" +msgstr "Slic3r에 오류가 발생했습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:84 +msgid "Save current " +msgstr "지금 저장" + +#: xs/src/slic3r/GUI/Tab.cpp:85 +msgid "Delete this preset" +msgstr "이전 설정 삭제" + +#: xs/src/slic3r/GUI/Tab.cpp:97 +msgid "" +"Hover the cursor over buttons to find more information \n" +"or click this button." +msgstr "" +"버튼 위로 커서를 가져 가서 자세한 정보를 찾습니다.\n" +"또는이 버튼을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:716 +msgid "It's a default preset." +msgstr "기본 설정입니다." + +#: xs/src/slic3r/GUI/Tab.cpp:717 +msgid "It's a system preset." +msgstr "시스템 설정입니다." + +#: xs/src/slic3r/GUI/Tab.cpp:718 +msgid "Current preset is inherited from " +msgstr "전의 설정에서 가져 옵니다" + +#: xs/src/slic3r/GUI/Tab.cpp:723 +msgid "It can't be deleted or modified. " +msgstr "삭제하거나 수정할 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:724 +msgid "Any modifications should be saved as a new preset inherited from this one. " +msgstr "모든 수정 사항은 이 항목에서 받은 기본 설정으로 저장해야합니다" + +#: xs/src/slic3r/GUI/Tab.cpp:725 +msgid "To do that please specify a new name for the preset." +msgstr "그렇게하려면 기본 설정의 새 이름을 지정하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:729 +msgid "Additional information:" +msgstr "추가 정보:" + +#: xs/src/slic3r/GUI/Tab.cpp:737 +msgid "printer model" +msgstr "프린터 모델" + +#: xs/src/slic3r/GUI/Tab.cpp:739 +msgid "default print profile" +msgstr "기본 인쇄 프로파일" + +#: xs/src/slic3r/GUI/Tab.cpp:742 +msgid "default filament profile" +msgstr "기본 필라멘트 프로파일" + +#: xs/src/slic3r/GUI/Tab.cpp:786 +msgid "Layers and perimeters" +msgstr "레이어 및 경계선" + +#: xs/src/slic3r/GUI/Tab.cpp:787 xs/src/libslic3r/PrintConfig.cpp:886 +msgid "Layer height" +msgstr "레이어 높이" + +#: xs/src/slic3r/GUI/Tab.cpp:791 +msgid "Vertical shells" +msgstr "쉘 높이" + +#: xs/src/slic3r/GUI/Tab.cpp:802 +msgid "Horizontal shells" +msgstr "쉘 너비" + +#: xs/src/slic3r/GUI/Tab.cpp:803 xs/src/libslic3r/PrintConfig.cpp:1562 +msgid "Solid layers" +msgstr "솔리드 레이어" + +#: xs/src/slic3r/GUI/Tab.cpp:808 +msgid "Quality (slower slicing)" +msgstr "품질(슬라이싱이 느려짐)" + +#: xs/src/slic3r/GUI/Tab.cpp:815 xs/src/slic3r/GUI/Tab.cpp:829 +#: xs/src/slic3r/GUI/Tab.cpp:923 xs/src/slic3r/GUI/Tab.cpp:926 +#: xs/src/slic3r/GUI/Tab.cpp:1276 xs/src/slic3r/GUI/Tab.cpp:1625 +#: xs/src/libslic3r/PrintConfig.cpp:110 xs/src/libslic3r/PrintConfig.cpp:245 +#: xs/src/libslic3r/PrintConfig.cpp:833 xs/src/libslic3r/PrintConfig.cpp:2021 +msgid "Advanced" +msgstr "고급" + +#: xs/src/slic3r/GUI/Tab.cpp:819 xs/src/slic3r/GUI/Tab.cpp:820 +#: xs/src/slic3r/GUI/Tab.cpp:1127 xs/src/libslic3r/PrintConfig.cpp:90 +#: xs/src/libslic3r/PrintConfig.cpp:284 xs/src/libslic3r/PrintConfig.cpp:585 +#: xs/src/libslic3r/PrintConfig.cpp:599 xs/src/libslic3r/PrintConfig.cpp:637 +#: xs/src/libslic3r/PrintConfig.cpp:778 xs/src/libslic3r/PrintConfig.cpp:788 +#: xs/src/libslic3r/PrintConfig.cpp:806 xs/src/libslic3r/PrintConfig.cpp:824 +#: xs/src/libslic3r/PrintConfig.cpp:843 xs/src/libslic3r/PrintConfig.cpp:1511 +#: xs/src/libslic3r/PrintConfig.cpp:1528 +msgid "Infill" +msgstr "인필(채움)" + +#: xs/src/slic3r/GUI/Tab.cpp:825 +msgid "Reducing printing time" +msgstr "출력 시간 단축" + +#: xs/src/slic3r/GUI/Tab.cpp:837 +msgid "Skirt and brim" +msgstr "스커트와 브림" + +#: xs/src/slic3r/GUI/Tab.cpp:838 xs/src/libslic3r/GCode/PreviewData.cpp:171 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:98 +msgid "Skirt" +msgstr "스커트" + +#: xs/src/slic3r/GUI/Tab.cpp:847 xs/src/slic3r/GUI/Tab.cpp:848 +#: xs/src/libslic3r/PrintConfig.cpp:228 xs/src/libslic3r/PrintConfig.cpp:1278 +#: xs/src/libslic3r/PrintConfig.cpp:1628 xs/src/libslic3r/PrintConfig.cpp:1635 +#: xs/src/libslic3r/PrintConfig.cpp:1647 xs/src/libslic3r/PrintConfig.cpp:1657 +#: xs/src/libslic3r/PrintConfig.cpp:1665 xs/src/libslic3r/PrintConfig.cpp:1680 +#: xs/src/libslic3r/PrintConfig.cpp:1701 xs/src/libslic3r/PrintConfig.cpp:1712 +#: xs/src/libslic3r/PrintConfig.cpp:1728 xs/src/libslic3r/PrintConfig.cpp:1737 +#: xs/src/libslic3r/PrintConfig.cpp:1746 xs/src/libslic3r/PrintConfig.cpp:1757 +#: xs/src/libslic3r/PrintConfig.cpp:1771 xs/src/libslic3r/PrintConfig.cpp:1779 +#: xs/src/libslic3r/PrintConfig.cpp:1780 xs/src/libslic3r/PrintConfig.cpp:1789 +#: xs/src/libslic3r/PrintConfig.cpp:1797 xs/src/libslic3r/PrintConfig.cpp:1811 +#: xs/src/libslic3r/GCode/PreviewData.cpp:172 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:99 +msgid "Support material" +msgstr "서포트 재료(Support material)" + +#: xs/src/slic3r/GUI/Tab.cpp:853 +msgid "Raft" +msgstr "라프트" + +#: xs/src/slic3r/GUI/Tab.cpp:857 +msgid "Options for support material and raft" +msgstr "서포트와 라프트 재료를 선택" + +#: xs/src/slic3r/GUI/Tab.cpp:871 xs/src/libslic3r/PrintConfig.cpp:122 +#: xs/src/libslic3r/PrintConfig.cpp:315 xs/src/libslic3r/PrintConfig.cpp:732 +#: xs/src/libslic3r/PrintConfig.cpp:844 xs/src/libslic3r/PrintConfig.cpp:1212 +#: xs/src/libslic3r/PrintConfig.cpp:1449 xs/src/libslic3r/PrintConfig.cpp:1499 +#: xs/src/libslic3r/PrintConfig.cpp:1550 xs/src/libslic3r/PrintConfig.cpp:1871 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:77 +msgid "Speed" +msgstr "속도" + +#: xs/src/slic3r/GUI/Tab.cpp:872 +msgid "Speed for print moves" +msgstr "출력중 이동 속도" + +#: xs/src/slic3r/GUI/Tab.cpp:884 +msgid "Speed for non-print moves" +msgstr "미출력시 이동속도" + +#: xs/src/slic3r/GUI/Tab.cpp:887 +msgid "Modifiers" +msgstr "수정" + +#: xs/src/slic3r/GUI/Tab.cpp:890 +msgid "Acceleration control (advanced)" +msgstr "가속 제어(고급)" + +#: xs/src/slic3r/GUI/Tab.cpp:897 +msgid "Autospeed (advanced)" +msgstr "오토스피트(고급)" + +#: xs/src/slic3r/GUI/Tab.cpp:903 +msgid "Multiple Extruders" +msgstr "다중 익스트루더" + +#: xs/src/slic3r/GUI/Tab.cpp:904 xs/src/slic3r/GUI/Tab.cpp:1451 +#: xs/src/libslic3r/PrintConfig.cpp:345 xs/src/libslic3r/PrintConfig.cpp:799 +#: xs/src/libslic3r/PrintConfig.cpp:1191 xs/src/libslic3r/PrintConfig.cpp:1520 +#: xs/src/libslic3r/PrintConfig.cpp:1693 xs/src/libslic3r/PrintConfig.cpp:1719 +#: xs/src/libslic3r/PrintConfig.cpp:1995 xs/src/libslic3r/PrintConfig.cpp:2004 +msgid "Extruders" +msgstr "익스트루더" + +#: xs/src/slic3r/GUI/Tab.cpp:911 +msgid "Ooze prevention" +msgstr "오즈 방지(Ooze prevention)" + +#: xs/src/slic3r/GUI/Tab.cpp:915 xs/src/libslic3r/GCode/PreviewData.cpp:174 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:101 +msgid "Wipe tower" +msgstr "와이프 타워(Wipe tower)" + +#: xs/src/slic3r/GUI/Tab.cpp:927 +msgid "Extrusion width" +msgstr "악출 폭(Extrusion width)" + +#: xs/src/slic3r/GUI/Tab.cpp:937 +msgid "Overlap" +msgstr "겹침(Overlap)" + +#: xs/src/slic3r/GUI/Tab.cpp:940 +msgid "Flow" +msgstr "유량(Flow)" + +#: xs/src/slic3r/GUI/Tab.cpp:943 +msgid "Other" +msgstr "그 외" + +#: xs/src/slic3r/GUI/Tab.cpp:950 +msgid "Output options" +msgstr "출력 옵션" + +#: xs/src/slic3r/GUI/Tab.cpp:951 +msgid "Sequential printing" +msgstr "연속 인쇄" + +#: xs/src/slic3r/GUI/Tab.cpp:953 +msgid "Extruder clearance (mm)" +msgstr "익스트루더 간격(mm)" + +#: xs/src/slic3r/GUI/Tab.cpp:962 +msgid "Output file" +msgstr "출력 파일" + +#: xs/src/slic3r/GUI/Tab.cpp:968 xs/src/libslic3r/PrintConfig.cpp:1234 +msgid "Post-processing scripts" +msgstr "포스트 프로세싱 스크립트" + +#: xs/src/slic3r/GUI/Tab.cpp:974 xs/src/slic3r/GUI/Tab.cpp:975 +#: xs/src/slic3r/GUI/Tab.cpp:1329 xs/src/slic3r/GUI/Tab.cpp:1330 +#: xs/src/slic3r/GUI/Tab.cpp:1668 xs/src/slic3r/GUI/Tab.cpp:1669 +msgid "Notes" +msgstr "메모" + +#: xs/src/slic3r/GUI/Tab.cpp:981 xs/src/slic3r/GUI/Tab.cpp:1337 +#: xs/src/slic3r/GUI/Tab.cpp:1675 +msgid "Dependencies" +msgstr "속한 그룹" + +#: xs/src/slic3r/GUI/Tab.cpp:982 xs/src/slic3r/GUI/Tab.cpp:1338 +#: xs/src/slic3r/GUI/Tab.cpp:1676 +msgid "Profile dependencies" +msgstr "프로파일 종석성" + +#: xs/src/slic3r/GUI/Tab.cpp:983 xs/src/slic3r/GUI/Tab.cpp:1339 +#: xs/src/slic3r/GUI/Tab.cpp:2364 xs/src/libslic3r/PrintConfig.cpp:147 +msgid "Compatible printers" +msgstr "호환 가능한 프린터들" + +#: xs/src/slic3r/GUI/Tab.cpp:1016 +#, no-c-format +msgid "" +"The Spiral Vase mode requires:\n" +"- one perimeter\n" +"- no top solid layers\n" +"- 0% fill density\n" +"- no support material\n" +"- no ensure_vertical_shell_thickness\n" +"\n" +"Shall I adjust those settings in order to enable Spiral Vase?" +msgstr "" +"스파이럴 바이스 모드에는 다음이 필요합니다.\n" +"- one 둘레\n" +"- 탑 솔리드 레이어 없음\n" +"- 채우기(fill) 밀도 0 %\n" +"- 서포트 재료 없음\n" +"- 수직 벽 두깨를 보장하지 않음\n" +"\n" +"스파이럴 바이스를 사용하려면 이러한 설정을 조정해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1023 +msgid "Spiral Vase" +msgstr "스파이럴 바이스" + +#: xs/src/slic3r/GUI/Tab.cpp:1044 +msgid "" +"The Wipe Tower currently supports the non-soluble supports only\n" +"if they are printed with the current extruder without triggering a tool change.\n" +"(both support_material_extruder and support_material_interface_extruder need to be set to 0).\n" +"\n" +"Shall I adjust those settings in order to enable the Wipe Tower?" +msgstr "" +"와이퍼 타워는 현재 비 가용성 서포트 만 지원합니다.\n" +"공구 교환을 트리거하지 않고 현재 압출기로 인쇄 한 경우.\n" +"(support_material_extruder 및 support_material_interface_extruder를 모두 0으로 설정해야 함).\n" +"\n" +"와이퍼 타워를 사용하려면 이러한 설정을 조정해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1048 xs/src/slic3r/GUI/Tab.cpp:1065 +msgid "Wipe Tower" +msgstr "와이프 타워(Wipe Tower)" + +#: xs/src/slic3r/GUI/Tab.cpp:1062 +msgid "" +"For the Wipe Tower to work with the soluble supports, the support layers\n" +"need to be synchronized with the object layers.\n" +"\n" +"Shall I synchronize support layers in order to enable the Wipe Tower?" +msgstr "" +"와이퍼 타워가 가용성 서포트와 함께 작용하기 위해, 서포트 레이어\n" +"객체 레이어와 동기화되어야합니다.\n" +"\n" +"와이퍼 타워를 사용하려면 서포트 레이어를 동기화해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1080 +msgid "" +"Supports work better, if the following feature is enabled:\n" +"- Detect bridging perimeters\n" +"\n" +"Shall I adjust those settings for supports?" +msgstr "" +"다음 기능을 사용하는 경우 더 나은 작업을 지원합니다.\n" +"- 브리지 경계 검출\n" +"\n" +"서포트에 대한 설정을 조정해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1083 +msgid "Support Generator" +msgstr "서포트 생성" + +#: xs/src/slic3r/GUI/Tab.cpp:1125 +msgid "The " +msgstr "The" + +#: xs/src/slic3r/GUI/Tab.cpp:1125 +#, no-c-format +msgid "" +" infill pattern is not supposed to work at 100% density.\n" +"\n" +"Shall I switch to rectilinear fill pattern?" +msgstr "" +"infill 패턴은 100 % 밀도에서 작동하지 않습니다.\n" +"\n" +"직선 채우기 패턴으로 전환해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1231 xs/src/slic3r/GUI/Tab.cpp:1232 +#: lib/Slic3r/GUI/Plater.pm:454 +msgid "Filament" +msgstr "필라멘트" + +#: xs/src/slic3r/GUI/Tab.cpp:1239 +msgid "Temperature " +msgstr "온도" + +#: xs/src/slic3r/GUI/Tab.cpp:1240 xs/src/libslic3r/PrintConfig.cpp:344 +msgid "Extruder" +msgstr "익스트루더(Extruder)" + +#: xs/src/slic3r/GUI/Tab.cpp:1245 +msgid "Bed" +msgstr "배드(Bed)" + +#: xs/src/slic3r/GUI/Tab.cpp:1250 +msgid "Cooling" +msgstr "냉각(Cooling)" + +#: xs/src/slic3r/GUI/Tab.cpp:1251 xs/src/libslic3r/PrintConfig.cpp:1137 +#: xs/src/libslic3r/PrintConfig.cpp:1941 +msgid "Enable" +msgstr "사용" + +#: xs/src/slic3r/GUI/Tab.cpp:1262 +msgid "Fan settings" +msgstr "팬 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1263 +msgid "Fan speed" +msgstr "팬 속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1271 +msgid "Cooling thresholds" +msgstr "냉각 임계 값" + +#: xs/src/slic3r/GUI/Tab.cpp:1277 +msgid "Filament properties" +msgstr "필라멘트 특성" + +#: xs/src/slic3r/GUI/Tab.cpp:1281 +msgid "Print speed override" +msgstr "인쇄 속도 중단" + +#: xs/src/slic3r/GUI/Tab.cpp:1291 +msgid "Toolchange parameters with single extruder MM printers" +msgstr "싱글 익스트루더 MM 프린터를 사용한 공구 교환 매개 변수" + +#: xs/src/slic3r/GUI/Tab.cpp:1299 +msgid "Ramming" +msgstr "래미" + +#: xs/src/slic3r/GUI/Tab.cpp:1301 +msgid "Ramming settings" +msgstr "래밍 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1316 xs/src/slic3r/GUI/Tab.cpp:1631 +msgid "Custom G-code" +msgstr "수동 G코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1317 xs/src/slic3r/GUI/Tab.cpp:1632 +#: xs/src/libslic3r/PrintConfig.cpp:1590 xs/src/libslic3r/PrintConfig.cpp:1605 +msgid "Start G-code" +msgstr "스타트 G코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1323 xs/src/slic3r/GUI/Tab.cpp:1638 +#: xs/src/libslic3r/PrintConfig.cpp:254 xs/src/libslic3r/PrintConfig.cpp:264 +msgid "End G-code" +msgstr "엔드 G코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1419 xs/src/slic3r/GUI/Preferences.cpp:17 +msgid "General" +msgstr "일반" + +#: xs/src/slic3r/GUI/Tab.cpp:1420 +msgid "Size and coordinates" +msgstr "크기와 좌표" + +#: xs/src/slic3r/GUI/Tab.cpp:1422 xs/src/libslic3r/PrintConfig.cpp:37 +msgid "Bed shape" +msgstr "배드 모양" + +#: xs/src/slic3r/GUI/Tab.cpp:1424 xs/src/slic3r/GUI/Tab.cpp:2332 +msgid " Set " +msgstr " 세트 " + +#: xs/src/slic3r/GUI/Tab.cpp:1447 +msgid "Capabilities" +msgstr "기능" + +#: xs/src/slic3r/GUI/Tab.cpp:1452 +msgid "Number of extruders of the printer." +msgstr "프린터 익스트루더 숫자" + +#: xs/src/slic3r/GUI/Tab.cpp:1477 +msgid "USB/Serial connection" +msgstr "USB/시리얼 연결" + +#: xs/src/slic3r/GUI/Tab.cpp:1478 xs/src/libslic3r/PrintConfig.cpp:1441 +msgid "Serial port" +msgstr "시리얼 포트" + +#: xs/src/slic3r/GUI/Tab.cpp:1483 +msgid "Rescan serial ports" +msgstr "시리얼포트 재검색" + +#: xs/src/slic3r/GUI/Tab.cpp:1492 xs/src/slic3r/GUI/Tab.cpp:1539 +msgid "Test" +msgstr "시험(test)" + +#: xs/src/slic3r/GUI/Tab.cpp:1505 +msgid "Connection to printer works correctly." +msgstr "프린터 연결이 올바르게 작동합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1505 xs/src/slic3r/GUI/Tab.cpp:1549 +msgid "Success!" +msgstr "성공!" + +#: xs/src/slic3r/GUI/Tab.cpp:1508 +msgid "Connection failed." +msgstr "연결 실패" + +#: xs/src/slic3r/GUI/Tab.cpp:1520 xs/src/slic3r/Utils/OctoPrint.cpp:110 +msgid "OctoPrint upload" +msgstr "옥토프린트 업로드" + +#: xs/src/slic3r/GUI/Tab.cpp:1523 xs/src/slic3r/GUI/Tab.cpp:1572 +msgid " Browse " +msgstr " 검색 " + +#: xs/src/slic3r/GUI/Tab.cpp:1549 +msgid "Connection to OctoPrint works correctly." +msgstr "OctoPrint에 연결하면 올바르게 작동합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1552 +msgid "Could not connect to OctoPrint" +msgstr "OctoPrint에 연결할 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1552 +msgid "Note: OctoPrint version at least 1.1.0 is required." +msgstr "참고 : OctoPrint 버전 1.1.0 이상이 필요합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1578 +msgid "Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*" +msgstr "인증서 파일 (* .crt, * .pem) | * .crt; * .pem | 모든 파일 | *. *" + +#: xs/src/slic3r/GUI/Tab.cpp:1579 +msgid "Open CA certificate file" +msgstr "Open CA certificate file" + +#: xs/src/slic3r/GUI/Tab.cpp:1593 +msgid "HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate." +msgstr "HTTPS CA 파일은 선택 사항입니다. 자체 서명 된 인증서로 HTTPS를 사용하는 경우에만 필요합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1644 xs/src/libslic3r/PrintConfig.cpp:51 +msgid "Before layer change G-code" +msgstr "레이어 변경 전 G 코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1650 xs/src/libslic3r/PrintConfig.cpp:875 +msgid "After layer change G-code" +msgstr "레이어 변경 후 G 코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1656 xs/src/libslic3r/PrintConfig.cpp:1848 +msgid "Tool change G-code" +msgstr "툴 채인지 G 코드" + +#: xs/src/slic3r/GUI/Tab.cpp:1662 +msgid "Between objects G-code (for sequential printing)" +msgstr "객체 간 G 코드 (순차 인쇄용)" + +#: xs/src/slic3r/GUI/Tab.cpp:1717 xs/src/slic3r/GUI/Tab.cpp:1778 +#: xs/src/slic3r/GUI/Tab.cpp:2037 xs/src/libslic3r/PrintConfig.cpp:920 +#: xs/src/libslic3r/PrintConfig.cpp:929 xs/src/libslic3r/PrintConfig.cpp:938 +#: xs/src/libslic3r/PrintConfig.cpp:950 xs/src/libslic3r/PrintConfig.cpp:960 +#: xs/src/libslic3r/PrintConfig.cpp:970 xs/src/libslic3r/PrintConfig.cpp:980 +msgid "Machine limits" +msgstr "머신 한계설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1730 +msgid "Values in this column are for Full Power mode" +msgstr "이 열의 값은 최대 전력 모드입니다" + +#: xs/src/slic3r/GUI/Tab.cpp:1731 +msgid "Full Power" +msgstr "최대 파워" + +#: xs/src/slic3r/GUI/Tab.cpp:1736 +msgid "Values in this column are for Silent mode" +msgstr "이 열의 값은 무음 모드 용입니다." + +#: xs/src/slic3r/GUI/Tab.cpp:1737 +msgid "Silent" +msgstr "무음" + +#: xs/src/slic3r/GUI/Tab.cpp:1745 +msgid "Maximum feedrates" +msgstr "최대 이송속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1750 +msgid "Maximum accelerations" +msgstr "최고 가속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1757 +msgid "Jerk limits" +msgstr "저크(Jerk)값 한계" + +#: xs/src/slic3r/GUI/Tab.cpp:1762 +msgid "Minimum feedrates" +msgstr "최대 이송속도" + +#: xs/src/slic3r/GUI/Tab.cpp:1800 xs/src/slic3r/GUI/Tab.cpp:1808 +#: xs/src/slic3r/GUI/Tab.cpp:2037 +msgid "Single extruder MM setup" +msgstr "싱글 익스트루더 MM 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:1809 +msgid "Single extruder multimaterial parameters" +msgstr "싱글 익스트루더 멀티메터리알 파라미터" + +#: xs/src/slic3r/GUI/Tab.cpp:1822 xs/src/libslic3r/GCode/PreviewData.cpp:446 +#, c-format +msgid "Extruder %d" +msgstr "익스트루더 %d" + +#: xs/src/slic3r/GUI/Tab.cpp:1829 +msgid "Layer height limits" +msgstr "레이어 높이 한계치" + +#: xs/src/slic3r/GUI/Tab.cpp:1834 +msgid "Position (for multi-extruder printers)" +msgstr "위치 (멀티 익스트루더 프린터 포함)" + +#: xs/src/slic3r/GUI/Tab.cpp:1837 +msgid "Retraction" +msgstr "리트렉션" + +#: xs/src/slic3r/GUI/Tab.cpp:1840 +msgid "Only lift Z" +msgstr "Z축만 올림" + +#: xs/src/slic3r/GUI/Tab.cpp:1853 +msgid "Retraction when tool is disabled (advanced settings for multi-extruder setups)" +msgstr "도구 비활성화시 리트렉션 (멀티 익스트루더 고급 설정)" + +#: xs/src/slic3r/GUI/Tab.cpp:1857 lib/Slic3r/GUI/Plater.pm:217 +#: lib/Slic3r/GUI/Plater.pm:2324 +msgid "Preview" +msgstr "프리뷰" + +#: xs/src/slic3r/GUI/Tab.cpp:1953 +msgid "" +"The Wipe option is not available when using the Firmware Retraction mode.\n" +"\n" +"Shall I disable it in order to enable Firmware Retraction?" +msgstr "" +"펌웨어 리트렉션 모드를 사용할 때는 Wipe 옵션을 사용할 수 없습니다.\n" +"\n" +"펌웨어 리트렉션 하려면 비활성화해야합니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:1955 +msgid "Firmware Retraction" +msgstr "펌웨어 레트렉션" + +#: xs/src/slic3r/GUI/Tab.cpp:2130 +msgid "Default " +msgstr "기본값 " + +#: xs/src/slic3r/GUI/Tab.cpp:2130 +msgid " preset" +msgstr " 기본 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:2131 +msgid " preset\n" +msgstr " 기본설정\n" + +#: xs/src/slic3r/GUI/Tab.cpp:2149 +msgid "" +"\n" +"\n" +"is not compatible with printer\n" +msgstr "" +"\n" +"\n" +"프린터와 호완 되지 않습니다.\n" + +#: xs/src/slic3r/GUI/Tab.cpp:2149 +msgid "" +"\n" +"\n" +"and it has the following unsaved changes:" +msgstr "" +"\n" +"\n" +"저장되지 않은 변경점은 다음과 같습니다:" + +#: xs/src/slic3r/GUI/Tab.cpp:2150 +msgid "" +"\n" +"\n" +"has the following unsaved changes:" +msgstr "" +"\n" +"\n" +"저장되지 않은 수정사항:" + +#: xs/src/slic3r/GUI/Tab.cpp:2152 +msgid "" +"\n" +"\n" +"Discard changes and continue anyway?" +msgstr "" +"\n" +"\n" +"수정된 사항을 취소하고 계속하겠습니까?" + +#: xs/src/slic3r/GUI/Tab.cpp:2153 +msgid "Unsaved Changes" +msgstr "미 저장된 변경점" + +#: xs/src/slic3r/GUI/Tab.cpp:2240 +msgid "The supplied name is empty. It can't be saved." +msgstr "파일 이름이 비어 있습니다. 저장할 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2245 +msgid "Cannot overwrite a system profile." +msgstr "시스템 프로파일을 겹쳐 쓸 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2249 +msgid "Cannot overwrite an external profile." +msgstr "외부 프로필을 덮어 쓸 수 없습니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2275 +msgid "remove" +msgstr "제거(remove)" + +#: xs/src/slic3r/GUI/Tab.cpp:2275 +msgid "delete" +msgstr "지우기(delete)" + +#: xs/src/slic3r/GUI/Tab.cpp:2276 +msgid "Are you sure you want to " +msgstr "정말로 다음과 같이 하겠습니까? " + +#: xs/src/slic3r/GUI/Tab.cpp:2276 +msgid " the selected preset?" +msgstr " 를(가) 선택된 설정을 실행 할까요?" + +#: xs/src/slic3r/GUI/Tab.cpp:2277 +msgid "Remove" +msgstr "제거(remove)" + +#: xs/src/slic3r/GUI/Tab.cpp:2277 lib/Slic3r/GUI/Plater.pm:251 +#: lib/Slic3r/GUI/Plater.pm:269 lib/Slic3r/GUI/Plater.pm:2215 +msgid "Delete" +msgstr "지우기(delete)" + +#: xs/src/slic3r/GUI/Tab.cpp:2278 +msgid " Preset" +msgstr " 기본 설정" + +#: xs/src/slic3r/GUI/Tab.cpp:2331 +msgid "All" +msgstr "모두 선택" + +#: xs/src/slic3r/GUI/Tab.cpp:2363 +msgid "Select the printers this profile is compatible with." +msgstr "이 프로파일과 호환 가능한 프린터를 선택하세요." + +#: xs/src/slic3r/GUI/Tab.cpp:2409 xs/src/slic3r/GUI/Tab.cpp:2495 +#: xs/src/slic3r/GUI/Preset.cpp:702 xs/src/slic3r/GUI/Preset.cpp:742 +#: xs/src/slic3r/GUI/Preset.cpp:770 xs/src/slic3r/GUI/Preset.cpp:802 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1193 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1246 lib/Slic3r/GUI/Plater.pm:603 +msgid "System presets" +msgstr "시스템 기본설정" + +#: xs/src/slic3r/GUI/Tab.cpp:2410 xs/src/slic3r/GUI/Tab.cpp:2496 +msgid "Default presets" +msgstr "시스템 기본값" + +#: xs/src/slic3r/GUI/Tab.cpp:2565 +msgid "LOCKED LOCK;indicates that the settings are the same as the system values for the current option group" +msgstr "자물쇠 잠금 : 설정이 현재 옵션 그룹의 시스템 값과 동일 함을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2568 +msgid "" +"UNLOCKED LOCK;indicates that some settings were changed and are not equal to the system values for the current option group.\n" +"Click the UNLOCKED LOCK icon to reset all settings for current option group to the system values." +msgstr "" +"잠금 풀림 : 일부 설정이 변경되었으며 현재 옵션 그룹의 시스템 값과 같지 않음을 나타냅니다.\n" +"현재 옵션 그룹의 모든 설정을 시스템 값으로 재설정하려면 자물쇠 잠금 아이콘을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:2574 +msgid "" +"WHITE BULLET;for the left button: \tindicates a non-system preset,\n" +"for the right button: \tindicates that the settings hasn't been modified." +msgstr "" +"흰색 총알; 왼쪽 버튼 : 시스템이 아닌 사전 설정을 나타내며,\n" +"오른쪽 버튼의 경우 : 설정이 수정되지 않았 음을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2578 +msgid "" +"BACK ARROW;indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" +"Click the BACK ARROW icon to reset all settings for the current option group to the last saved preset." +msgstr "" +"잠금 풀림;일부 설정이 변경되었으며 현재 옵션 그룹의 시스템 값과 같지 않음을 나타냅니다.\n" +"현재 옵션 그룹의 모든 설정을 시스템 값으로 재설정하려면 자물쇠 잠금 아이콘을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:2604 +msgid "LOCKED LOCK icon indicates that the settings are the same as the system values for the current option group" +msgstr "자물쇠 잠금 아이코 설정이 현재 옵션 그룹의 시스템 값과 동일 함을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2606 +msgid "" +"UNLOCKED LOCK icon indicates that some settings were changed and are not equal to the system values for the current option group.\n" +"Click to reset all settings for current option group to the system values." +msgstr "" +"잠금 풀림 아이코 일부 설정이 변경되었으며 현재 옵션 그룹의 시스템 값과 같지 않음을 나타냅니다.\n" +"현재 옵션 그룹의 모든 설정을 시스템 값으로 재설정하려면 자물쇠 잠금 아이콘을 클릭하십시오." + +#: xs/src/slic3r/GUI/Tab.cpp:2609 +msgid "WHITE BULLET icon indicates a non system preset." +msgstr "흰색 글머리 아이콘은 시스템 사전 설정이 아닌 것을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2612 +msgid "WHITE BULLET icon indicates that the settings are the same as in the last saved preset for the current option group." +msgstr "흰색 글머리 기호 아이콘은 설정이 현재 옵션 그룹에 대해 마지막으로 저장 된 사전 설정과 동일 하다는 것을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2614 +msgid "" +"BACK ARROW icon indicates that the settings were changed and are not equal to the last saved preset for the current option group.\n" +"Click to reset all settings for the current option group to the last saved preset." +msgstr "" +"백화살표 아이콘 설정을 변경 하 고 현재 옵션 그룹에 대 한 마지막 저장 된 프리셋을 동일 하지 않습니다 나타냅니다.\n" +"마지막 현재 옵션 그룹에 대 한 모든 설정 다시 설정을 클릭 하 여 사전 설정을 저장." + +#: xs/src/slic3r/GUI/Tab.cpp:2620 +msgid "LOCKED LOCK icon indicates that the value is the same as the system value." +msgstr "잠긴 자물쇠 아이콘 값 같은 시스템 값 임을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2621 +msgid "" +"UNLOCKED LOCK icon indicates that the value was changed and is not equal to the system value.\n" +"Click to reset current value to the system value." +msgstr "" +"잠금 해제 자물쇠 아이콘 값 변경 된 시스템 값은 나타냅니다.\n" +"시스템 값을 현재 값으로 설정 하려면 클릭 합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2627 +msgid "WHITE BULLET icon indicates that the value is the same as in the last saved preset." +msgstr "흰색 글머리 기호 아이콘은 마지막으로 저장 한 사전 설정과 동일한 값을 나타냅니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2628 +msgid "" +"BACK ARROW icon indicates that the value was changed and is not equal to the last saved preset.\n" +"Click to reset current value to the last saved preset." +msgstr "" +"잠금 해제 자물쇠 아이콘 값 변경 된 시스템 값은 나타냅니다.\n" +"시스템 값을 현재 값으로 설정 하려면 클릭 합니다." + +#: xs/src/slic3r/GUI/Tab.cpp:2703 lib/Slic3r/GUI/MainFrame.pm:469 +#: lib/Slic3r/GUI/Plater.pm:1795 +msgid "Save " +msgstr "저장 " + +#: xs/src/slic3r/GUI/Tab.cpp:2703 +msgid " as:" +msgstr " as:" + +#: xs/src/slic3r/GUI/Tab.cpp:2742 xs/src/slic3r/GUI/Tab.cpp:2746 +msgid "The supplied name is not valid;" +msgstr "제공된 이름이 유효하지 않습니다;" + +#: xs/src/slic3r/GUI/Tab.cpp:2743 +msgid "the following characters are not allowed:" +msgstr "다음 문자는 허용되지 않습니다:" + +#: xs/src/slic3r/GUI/Tab.cpp:2747 +msgid "the following postfix are not allowed:" +msgstr "다음 접미사는 허용되지 않습니다:" + +#: xs/src/slic3r/GUI/Tab.cpp:2750 +msgid "The supplied name is not available." +msgstr "The supplied name is not available." + +#: xs/src/slic3r/GUI/Tab.hpp:286 +msgid "Print Settings" +msgstr "출력 설정" + +#: xs/src/slic3r/GUI/Tab.hpp:306 +msgid "Filament Settings" +msgstr "필라멘트 설정" + +#: xs/src/slic3r/GUI/Tab.hpp:332 +msgid "Printer Settings" +msgstr "프린터 설정" + +#: xs/src/slic3r/GUI/Tab.hpp:348 +msgid "Save preset" +msgstr "사전 설정 저장" + +#: xs/src/slic3r/GUI/Field.cpp:98 +msgid "default" +msgstr "기본값 " + +#: xs/src/slic3r/GUI/Field.cpp:128 +#, c-format +msgid "%s doesn't support percentage" +msgstr "%s 이(가) 백분율을 지원하지 않음" + +#: xs/src/slic3r/GUI/Field.cpp:137 +msgid "Input value is out of range" +msgstr "Input value is out of range" + +#: xs/src/slic3r/GUI/Preset.cpp:144 +msgid "modified" +msgstr "수정된곳" + +#: xs/src/slic3r/GUI/Preset.cpp:746 xs/src/slic3r/GUI/Preset.cpp:806 +#: xs/src/slic3r/GUI/PresetBundle.cpp:1251 lib/Slic3r/GUI/Plater.pm:604 +msgid "User presets" +msgstr "사용자 사전설정" + +#: xs/src/slic3r/GUI/PresetHints.cpp:27 +#, c-format +msgid "If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s)." +msgstr "예상 레이어 시간이 ~ % d 초 미만이면 팬이 % d %%에서 실행되고 인쇄 속도가 감소되어 해당 레이어에 % ds 이상 소비됩니다 (단, 속도는 % dmm / s 이하로 감소하지 않습니다) ." + +#: xs/src/slic3r/GUI/PresetHints.cpp:31 +#, c-format +msgid "" +"\n" +"If estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%." +msgstr "" +"\n" +"예상 레이어 시간이 더 길지만 ~ % ds 미만인 경우 팬은 % d %%와 % d %% 사이에 비례하여 감소하는 속도로 실행됩니다." + +#: xs/src/slic3r/GUI/PresetHints.cpp:35 +msgid "" +"\n" +"During the other layers, fan " +msgstr "" +"\n" +"다른 레이어 중 팬 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:37 +msgid "Fan " +msgstr "팬(Fan) " + +#: xs/src/slic3r/GUI/PresetHints.cpp:42 +#, c-format +msgid "will always run at %d%% " +msgstr "항상 다음처럼 실행 %d%% " + +#: xs/src/slic3r/GUI/PresetHints.cpp:45 +#, c-format +msgid "except for the first %d layers" +msgstr "첫 번째 %d 레이어를 제외하고" + +#: xs/src/slic3r/GUI/PresetHints.cpp:49 +msgid "except for the first layer" +msgstr "첫 번째 레이어를 제외하고" + +#: xs/src/slic3r/GUI/PresetHints.cpp:51 +msgid "will be turned off." +msgstr "off 됩니다." + +#: xs/src/slic3r/GUI/PresetHints.cpp:152 +msgid "external perimeters" +msgstr "외부 둘레" + +#: xs/src/slic3r/GUI/PresetHints.cpp:161 +msgid "perimeters" +msgstr "둘레" + +#: xs/src/slic3r/GUI/PresetHints.cpp:170 +msgid "infill" +msgstr "채움(infill)" + +#: xs/src/slic3r/GUI/PresetHints.cpp:180 +msgid "solid infill" +msgstr "고체(solid)부분 채움" + +#: xs/src/slic3r/GUI/PresetHints.cpp:188 +msgid "top solid infill" +msgstr "가장 윗부분 채움" + +#: xs/src/slic3r/GUI/PresetHints.cpp:199 +msgid "support" +msgstr "서포트" + +#: xs/src/slic3r/GUI/PresetHints.cpp:209 +msgid "support interface" +msgstr "서포트 인터페이스" + +#: xs/src/slic3r/GUI/PresetHints.cpp:215 +msgid "First layer volumetric" +msgstr "첫번째 레이어 용적" + +#: xs/src/slic3r/GUI/PresetHints.cpp:215 +msgid "Bridging volumetric" +msgstr "브리징(Bridging) 용적" + +#: xs/src/slic3r/GUI/PresetHints.cpp:215 +msgid "Volumetric" +msgstr "용적" + +#: xs/src/slic3r/GUI/PresetHints.cpp:216 +msgid " flow rate is maximized " +msgstr "유속(flow)이 최대화된다." + +#: xs/src/slic3r/GUI/PresetHints.cpp:219 +msgid "by the print profile maximum" +msgstr "인쇄 프로파일 최대 값" + +#: xs/src/slic3r/GUI/PresetHints.cpp:220 +msgid "when printing " +msgstr "인쇄 할때 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:221 +msgid " with a volumetric rate " +msgstr " 용적 비율로 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:225 +#, c-format +msgid "%3.2f mm³/s" +msgstr "%3.2f mm³/s" + +#: xs/src/slic3r/GUI/PresetHints.cpp:227 +#, c-format +msgid " at filament speed %3.2f mm/s." +msgstr " 필라멘트 속도는 %3.2f mm/s." + +#: xs/src/slic3r/GUI/PresetHints.cpp:246 +msgid "Recommended object thin wall thickness: Not available due to invalid layer height." +msgstr "권장 객체(object) 벽(wall) 두께: 잘못된 레이어 높이 때문에 사용할 수 없음" + +#: xs/src/slic3r/GUI/PresetHints.cpp:263 +#, c-format +msgid "Recommended object thin wall thickness for layer height %.2f and " +msgstr "개체 레이어 높이 %.2f 에 대 한 얇은 벽 두께 권장 하 고 " + +#: xs/src/slic3r/GUI/PresetHints.cpp:270 +#, c-format +msgid "%d lines: %.2lf mm" +msgstr "%d 라인(lines): %.2lf mm" + +#: xs/src/slic3r/GUI/Preferences.cpp:34 +msgid "Remember output directory" +msgstr "출력 디렉토리 기억하기" + +#: xs/src/slic3r/GUI/Preferences.cpp:36 +msgid "If this is enabled, Slic3r will prompt the last output directory instead of the one containing the input files." +msgstr "이 옵션을 사용하면 Slic3r은 입력 파일이 들어있는 디렉터리 대신 마지막 출력 디렉터리를 묻습니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:42 +msgid "Auto-center parts" +msgstr "부품을 자동으로 중심에" + +#: xs/src/slic3r/GUI/Preferences.cpp:44 +msgid "If this is enabled, Slic3r will auto-center objects around the print bed center." +msgstr "이 옵션을 사용하면 Slic3r이 개체를 인쇄판 중앙에 자동으로 배치합니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:50 +msgid "Background processing" +msgstr "백그라운드 프로세싱" + +#: xs/src/slic3r/GUI/Preferences.cpp:52 +msgid "If this is enabled, Slic3r will pre-process objects as soon as they're loaded in order to save time when exporting G-code." +msgstr "이 사용 하는 경우 Slic3r는 전처리 개체 최대한 빨리 그들이 시간을 절약 하기 위해 로드 G-코드를 내보낼 때." + +#: xs/src/slic3r/GUI/Preferences.cpp:74 +msgid "Disable USB/serial connection" +msgstr "USB/시리얼 연결 비활성화" + +#: xs/src/slic3r/GUI/Preferences.cpp:76 +msgid "Disable communication with the printer over a serial / USB cable. This simplifies the user interface in case the printer is never attached to the computer." +msgstr "시리얼을 통해 프린터와의 통신을 사용 하지 않도록 설정 / USB 케이블. 이 프린터는 결코 컴퓨터에 연결 하는 경우에 사용자 인터페이스를 간소화 합니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:82 +msgid "Suppress \" - default - \" presets" +msgstr "\"- 기본 -\"사전 설정 숨기기" + +#: xs/src/slic3r/GUI/Preferences.cpp:84 +msgid "Suppress \" - default - \" presets in the Print / Filament / Printer selections once there are any other valid presets available." +msgstr "사용 가능한 다른 유효한 사전 설정이 있으면 인쇄 / 필라멘트 / 프린터 선택에서 \"- 기본 -\"사전 설정을 억제하십시오." + +#: xs/src/slic3r/GUI/Preferences.cpp:90 +msgid "Show incompatible print and filament presets" +msgstr "호환 되지 않는 인쇄 및 필라멘트 설정" + +#: xs/src/slic3r/GUI/Preferences.cpp:92 +msgid "When checked, the print and filament presets are shown in the preset editor even if they are marked as incompatible with the active printer" +msgstr "이 옵션을 선택하면 활성 프린터와 호환되지 않는 것으로 표시된 경우에도 인쇄 및 필라멘트 사전 설정이 사전 설정 편집기에 표시됩니다" + +#: xs/src/slic3r/GUI/Preferences.cpp:98 +msgid "Use legacy OpenGL 1.1 rendering" +msgstr "레거시 OpenGL 1.1 렌더링 사용" + +#: xs/src/slic3r/GUI/Preferences.cpp:100 +msgid "If you have rendering issues caused by a buggy OpenGL 2.0 driver, you may try to check this checkbox. This will disable the layer height editing and anti aliasing, so it is likely better to upgrade your graphics driver." +msgstr "버그가있는 OpenGL 2.0 드라이버로 인한 렌더링 문제가있는 경우이 확인란을 선택해보십시오. 이렇게하면 레이어 높이 편집 및 앤티 앨리어싱이 비활성화되므로 그래픽 드라이버를 업그레이드하는 것이 좋습니다." + +#: xs/src/slic3r/GUI/Preferences.cpp:124 +msgid "You need to restart Slic3r to make the changes effective." +msgstr "변경 사항을 적용하려면 Slic3r을 다시 시작해야합니다." + +#: xs/src/slic3r/GUI/RammingChart.cpp:23 +msgid "NO RAMMING AT ALL" +msgstr "전혀 충돌 없음" + +#: xs/src/slic3r/GUI/RammingChart.cpp:76 +msgid "Time" +msgstr "시간" + +#: xs/src/slic3r/GUI/RammingChart.cpp:76 xs/src/slic3r/GUI/RammingChart.cpp:81 +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:77 +#: xs/src/libslic3r/PrintConfig.cpp:490 +msgid "s" +msgstr "s" + +#: xs/src/slic3r/GUI/RammingChart.cpp:81 +msgid "Volumetric speed" +msgstr "용적(Volumetric) 스피트" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "Update available" +msgstr "사용가능한 업데이트" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:27 +msgid "New version of Slic3r PE is available" +msgstr "새로운 버전의 Slic3r PE 사용 가능" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:34 +msgid "To download, follow the link below." +msgstr "다운로드하려면 아래 링크를 클릭하십시오." + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:41 +msgid "Current version:" +msgstr "현재 버전:" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:43 +msgid "New version:" +msgstr "새로운 버전:" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:51 +msgid "Don't notify about new releases any more" +msgstr "새로운 수정사항에 대해 더 이상 알림 안 함" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:161 +msgid "Configuration update" +msgstr "구성 업데이트" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:69 +msgid "Configuration update is available" +msgstr "구성 업데이트를 사용할 수 있음" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:72 +msgid "" +"Would you like to install it?\n" +"\n" +"Note that a full configuration snapshot will be created first. It can then be restored at any time should there be a problem with the new version.\n" +"\n" +"Updated configuration bundles:" +msgstr "" +"그것을 설치 하시겠습니까?\n" +"\n" +"전체 구성 스냅 샷이 먼저 만들어집니다. 그런 다음 새 버전에 문제가있을 경우 언제든지 복원 할 수 있습니다.\n" +"\n" +"업데이트 된 구성 번들 :" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r incompatibility" +msgstr "Slic3r와 호환 되지 않음" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:108 +msgid "Slic3r configuration is incompatible" +msgstr "Slic3r 구성이 호환되지 않습니다." + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:111 +msgid "" +"This version of Slic3r PE is not compatible with currently installed configuration bundles.\n" +"This probably happened as a result of running an older Slic3r PE after using a newer one.\n" +"\n" +"You may either exit Slic3r and try again with a newer version, or you may re-run the initial configuration. Doing so will create a backup snapshot of the existing configuration before installing files compatible with this Slic3r.\n" +msgstr "" +"이 버전의 Slic3r PE는 현재 설치된 구성 번들과 호환되지 않습니다.\n" +"이것은 아마도 새로운 Slic3r PE를 사용한 후에 실행 된 결과 일 것입니다.\n" +"\n" +"Slic3r을 종료하고 새 버전으로 다시 시도하거나 초기 구성을 다시 실행할 수 있습니다. 이렇게하면이 Slic3r과 호환되는 파일을 설치하기 전에 기존 구성의 백업 스냅 샷을 생성 할 수 있습니다.\n" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:120 +#, c-format +msgid "This Slic3r PE version: %s" +msgstr "이 Slic3r PE 버전 : % s" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:125 +msgid "Incompatible bundles:" +msgstr "호환되지 않는 번들 :" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:141 +msgid "Exit Slic3r" +msgstr "Exit Slic3r" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:144 +msgid "Re-configure" +msgstr "재구성" + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:165 +#, c-format +msgid "" +"Slic3r PE now uses an updated configuration structure.\n" +"\n" +"So called 'System presets' have been introduced, which hold the built-in default settings for various printers. These System presets cannot be modified, instead, users now may create their own presets inheriting settings from one of the System presets.\n" +"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n" +"\n" +"Please proceed with the %s that follows to set up the new presets and to choose whether to enable automatic preset updates." +msgstr "" +"Slic3r PE는 이제 업데이트 된 구성 구조를 사용합니다.\n" +"\n" +"'시스템 사전 설정'이 도입되어 다양한 프린터에 기본 제공되는 기본 설정이 유지됩니다. 이러한 시스템 사전 설정은 수정할 수 없으며 대신 사용자는 시스템 사전 설정 중 하나에서 설정을 상속하는 자체 사전 설정을 만들 수 있습니다.\n" +"상속 된 사전 설정은 부모로부터 특정 값을 상속 받거나 사용자 정의 값으로 대체 할 수 있습니다.\n" +"\n" +"새 사전 설정을 설정하고 자동 사전 설정 업데이트를 사용할지 여부를 선택하려면 다음의 % s을 계속 진행하십시오." + +#: xs/src/slic3r/GUI/UpdateDialogs.cpp:181 +msgid "For more information please visit our wiki page:" +msgstr "자세한 정보는 위키 페이지를 참조하십시오 :" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:9 +msgid "Ramming customization" +msgstr "사용자 정의 다지기(Ramming)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:35 +msgid "" +"Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself be reinserted later. This phase is important and different materials can require different extrusion speeds to get the good shape. For this reason, the extrusion rates during ramming are adjustable.\n" +"\n" +"This is an expert-level setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc." +msgstr "" +"래밍은 단일 압출기 MM 프린터에서 공구 교환 직전의 신속한 압출을 나타냅니다. 그 목적은 언로드 된 필라멘트의 끝 부분을 적절히 형성하여 새로운 필라멘트의 삽입을 방지하고 나중에 다시 삽입 할 수 있도록하기위한 것입니다. 이 단계는 중요하며 다른 재료는 좋은 모양을 얻기 위해 다른 압출 속도를 요구할 수 있습니다. 이러한 이유로, 래밍 중 압출 속도는 조정 가능합니다.\n" +"\n" +"전문가 수준의 설정이므로 잘못된 조정으로 인해 용지 걸림, 압출기 휠이 필라멘트 등에 연삭 될 수 있습니다." + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:77 +msgid "Total ramming time" +msgstr "총 래밍 시간" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:79 +msgid "Total rammed volume" +msgstr "총 레미드 양" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:83 +msgid "Ramming line width" +msgstr "래밍 선 너비" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:85 +msgid "Ramming line spacing" +msgstr "래밍 선 간격" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:137 +msgid "Wipe tower - Purging volume adjustment" +msgstr "와이프 타워 - 버려진 필라멘트 조절" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:218 +msgid "Here you can adjust required purging volume (mm³) for any given pair of tools." +msgstr "여기서 주어진 도구 쌍에 필요한 정화 용량 (mm³)을 조정할 수 있습니다." + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:219 +msgid "Extruder changed to" +msgstr "익스트루더 번경" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:227 +msgid "unloaded" +msgstr "언로드(unloaded)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:228 +msgid "loaded" +msgstr "로드(loaded)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:233 +msgid "Tool #" +msgstr "툴(Tool) #" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:240 +msgid "Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded." +msgstr "총 정화 량은 어느 공구가로드 / 언로드되는지에 따라 아래의 두 값을 합산하여 계산됩니다." + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:241 +msgid "Volume to purge (mm³) when the filament is being" +msgstr "제거할 필라멘트 양 (mm³)" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:255 +msgid "From" +msgstr "From" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:320 +msgid "" +"Switching to simple settings will discard changes done in the advanced mode!\n" +"\n" +"Do you want to proceed?" +msgstr "" +"단순 설정으로 전환하면 고급 모드에서 수행된 변경 내용이 삭제됨!\n" +"\n" +"계속하시겠습니까?" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:332 +msgid "Show simplified settings" +msgstr "간단한 설정보기" + +#: xs/src/slic3r/GUI/WipeTowerDialog.cpp:332 +msgid "Show advanced settings" +msgstr "고급 설정보기" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Send G-Code to printer" +msgstr "프린터에 G 코드 전송" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:33 +msgid "Upload to OctoPrint with the following filename:" +msgstr "OctoPrint에 다음 파일명으로 업로드하십시오 :" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:35 +msgid "Start printing after upload" +msgstr "업로드 후 인쇄 시작" + +#: xs/src/slic3r/Utils/OctoPrint.cpp:37 +msgid "Use forward slashes ( / ) as a directory separator if needed." +msgstr "필요한 경우 디렉토리 분리 기호로 슬래시 (/)를 사용하십시오." + +#: xs/src/slic3r/Utils/OctoPrint.cpp:98 +msgid "Error while uploading to the OctoPrint server" +msgstr "OctoPrint 서버에 업로드하는 동안 오류가 발생했습니다." + +#: xs/src/slic3r/Utils/OctoPrint.cpp:111 lib/Slic3r/GUI/Plater.pm:1558 +msgid "Sending G-code file to the OctoPrint server..." +msgstr "OctoPrint 서버에 G 코드 파일 보내기 ..." + +#: xs/src/slic3r/Utils/PresetUpdater.cpp:544 +#, c-format +msgid "requires min. %s and max. %s" +msgstr "최소. %s 와 최대. %s" + +#: xs/src/libslic3r/Print.cpp:553 +msgid "All objects are outside of the print volume." +msgstr "모든 개체가 인쇄 볼륨 외부에 있습니다." + +#: xs/src/libslic3r/Print.cpp:579 +msgid "Some objects are too close; your extruder will collide with them." +msgstr "일부 개체가 너무 가깝습니다. 귀하의 압출기가 그들과 충돌합니다." + +#: xs/src/libslic3r/Print.cpp:594 +msgid "Some objects are too tall and cannot be printed without extruder collisions." +msgstr "일부 개체는 너무 크고 익스트루더 충돌없이 인쇄 할 수 없습니다." + +#: xs/src/libslic3r/Print.cpp:604 +msgid "The Spiral Vase option can only be used when printing a single object." +msgstr "나선형 꽃병(Spiral Vase) 옵션은 단일 개체를 인쇄 할 때만 사용할 수 있습니다." + +#: xs/src/libslic3r/Print.cpp:606 +msgid "The Spiral Vase option can only be used when printing single material objects." +msgstr "나선형 꽃병 옵션(Spiral Vase)은 단일 재료 객체를 인쇄 할 때만 사용할 수 있습니다." + +#: xs/src/libslic3r/Print.cpp:612 +msgid "All extruders must have the same diameter for single extruder multimaterial printer." +msgstr "모든 익스트루더는 멀티메터리얼 프린터의 싱글 익스트루더에 대해 동일한 직경을 가져야합니다." + +#: xs/src/libslic3r/Print.cpp:617 +msgid "The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors." +msgstr "현재 Wipe Tower는 Marlin 및 RepRap / Sprinter G 코드의 경우에만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:619 +msgid "The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)." +msgstr "와이프 타워는 현재 상대적 압출기 어드레싱 (use_relative_e_distances = 1)에서만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:631 +msgid "The Wipe Tower is only supported for multiple objects if they have equal layer heigths" +msgstr "와이프 타워 (Wipe Tower)는 같은 레이어 높이에 경우 여러 객체에 대해서만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:633 +msgid "The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers" +msgstr "와이프 타워는 같은 수의 라프트 레이어 위에 인쇄 된 경우 여러 객체에 대해서만 지원됩니다" + +#: xs/src/libslic3r/Print.cpp:635 +msgid "The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance" +msgstr "와이프 타워는 동일한 support_material_contact_distance로 인쇄 된 경우 여러 객체에 대해서만 지원됩니다" + +#: xs/src/libslic3r/Print.cpp:637 +msgid "The Wipe Tower is only supported for multiple objects if they are sliced equally." +msgstr "와이프 타워는 똑같이 슬라이스 된 경우 여러 오브젝트에 대해서만 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:661 +msgid "The Wipe tower is only supported if all objects have the same layer height profile" +msgstr "모든 오브젝트의 레이어 높이 프로필이 동일한 경우에만 와이프 타워가 지원됩니다." + +#: xs/src/libslic3r/Print.cpp:670 +msgid "The supplied settings will cause an empty print." +msgstr "제공된 설정으로 인해 빈 인쇄가 발생합니다." + +#: xs/src/libslic3r/Print.cpp:680 +msgid "One or more object were assigned an extruder that the printer does not have." +msgstr "하나 이상의 개체에 프린터에없는 압출기가 지정되었습니다." + +#: xs/src/libslic3r/Print.cpp:689 +msgid "Printing with multiple extruders of differing nozzle diameters. If support is to be printed with the current extruder (support_material_extruder == 0 or support_material_interface_extruder == 0), all nozzles have to be of the same diameter." +msgstr "노즐 지름이 다른 여러 압출기로 인쇄. 지원이 현재 압출기 (support_material_extruder == 0 또는 support_material_interface_extruder == 0)로 인쇄되는 경우 모든 노즐은 동일한 지름이어야합니다." + +#: xs/src/libslic3r/Print.cpp:695 +msgid "first_layer_height" +msgstr "first_layer_height" + +#: xs/src/libslic3r/Print.cpp:710 +msgid "First layer height can't be greater than nozzle diameter" +msgstr "첫번째 레이어 높이는 노즐 직경보다 클 수 없습니다." + +#: xs/src/libslic3r/Print.cpp:714 +msgid "Layer height can't be greater than nozzle diameter" +msgstr "레이어 높이는 노즐 직경보다 클 수 없습니다." + +#: xs/src/libslic3r/Print.cpp:1196 +msgid "Failed processing of the output_filename_format template." +msgstr "Failed processing of the output_filename_format template." + +#: xs/src/libslic3r/PrintConfig.cpp:29 +msgid "Avoid crossing perimeters" +msgstr "출력된 외측을 피하세요" + +#: xs/src/libslic3r/PrintConfig.cpp:30 +msgid "Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation." +msgstr "둘레의 교차를 최소화하기 위해 여행 이동을 최적화하십시오. 이것은 보 잉 (Bowling) 압출기가 흘러 나오기 쉬운 경우에 주로 유용합니다. 이 기능을 사용하면 인쇄 및 G 코드 생성 속도가 느려집니다." + +#: xs/src/libslic3r/PrintConfig.cpp:41 xs/src/libslic3r/PrintConfig.cpp:1818 +msgid "Other layers" +msgstr "다른 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:42 +msgid "Bed temperature for layers after the first one. Set this to zero to disable bed temperature control commands in the output." +msgstr "첫 번째 레이어 이후의 레이어 온도. 이 값을 0으로 설정하면 출력에서 ​​베드 온도 제어 명령을 비활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:45 +msgid "Bed temperature" +msgstr "배드 온도" + +#: xs/src/libslic3r/PrintConfig.cpp:52 +msgid "This custom code is inserted at every layer change, right before the Z move. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]." +msgstr "이 사용자 정의 코드는 Z 이동 직전의 모든 레이어 변경에 삽입됩니다. [Slide3r] 설정과 [layer_num] 및 [layer_z]에 대한 자리 표시 자 변수를 사용할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:62 +msgid "Between objects G-code" +msgstr "객체 간 G 코드" + +#: xs/src/libslic3r/PrintConfig.cpp:63 +msgid "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." +msgstr "이 코드는 순차 인쇄를 사용할 때 객체간에 삽입됩니다. 기본적으로 익스트루더 및 베드 온도는 대기 모드가 아닌 명령을 사용하여 재설정됩니다. 그러나 이 사용자 코드에서 M104, M109, M140 또는 M190이 감지되면 Slic3r은 온도 명령을 추가하지 않습니다. 모든 Slic3r 설정에 자리 표시 변수를 사용할 수 있으므로 원하는 위치에 \"M109 S [first_layer_temperature]\"명령을 넣을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:71 lib/Slic3r/GUI/MainFrame.pm:328 +msgid "Bottom" +msgstr "바닥(Bottom)" + +#: xs/src/libslic3r/PrintConfig.cpp:72 xs/src/libslic3r/PrintConfig.cpp:276 +#: xs/src/libslic3r/PrintConfig.cpp:327 xs/src/libslic3r/PrintConfig.cpp:335 +#: xs/src/libslic3r/PrintConfig.cpp:701 xs/src/libslic3r/PrintConfig.cpp:871 +#: xs/src/libslic3r/PrintConfig.cpp:887 xs/src/libslic3r/PrintConfig.cpp:1156 +#: xs/src/libslic3r/PrintConfig.cpp:1222 xs/src/libslic3r/PrintConfig.cpp:1400 +#: xs/src/libslic3r/PrintConfig.cpp:1829 xs/src/libslic3r/PrintConfig.cpp:1885 +msgid "Layers and Perimeters" +msgstr "레이어 및 경계선" + +#: xs/src/libslic3r/PrintConfig.cpp:73 +msgid "Number of solid layers to generate on bottom surfaces." +msgstr "바닥면에 생성 할 솔리드 레이어의 수." + +#: xs/src/libslic3r/PrintConfig.cpp:75 +msgid "Bottom solid layers" +msgstr "바닥 단일 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:80 +msgid "Bridge" +msgstr "브리지" + +#: xs/src/libslic3r/PrintConfig.cpp:81 +msgid "This is the acceleration your printer will use for bridges. Set zero to disable acceleration control for bridges." +msgstr "이것은 프린터가 브릿지에 사용할 가속도입니다. 브리지의 가속 제어를 사용하지 않으려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:83 xs/src/libslic3r/PrintConfig.cpp:199 +#: xs/src/libslic3r/PrintConfig.cpp:673 xs/src/libslic3r/PrintConfig.cpp:781 +#: xs/src/libslic3r/PrintConfig.cpp:931 xs/src/libslic3r/PrintConfig.cpp:972 +#: xs/src/libslic3r/PrintConfig.cpp:982 xs/src/libslic3r/PrintConfig.cpp:1185 +msgid "mm/s²" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:89 +msgid "Bridging angle" +msgstr "브릿지 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:91 +msgid "Bridging angle override. If left to zero, the bridging angle will be calculated automatically. Otherwise the provided angle will be used for all bridges. Use 180° for zero angle." +msgstr "브리징 각도 오버라이드(override)값이. 왼쪽으로 0 일 경우 브리징 각도가 자동으로 계산됩니다. 그렇지 않으면 제공된 각도가 모든 브리지에 사용됩니다. 각도 제로는 180 °를 사용하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:94 xs/src/libslic3r/PrintConfig.cpp:589 +#: xs/src/libslic3r/PrintConfig.cpp:1418 xs/src/libslic3r/PrintConfig.cpp:1429 +#: xs/src/libslic3r/PrintConfig.cpp:1649 xs/src/libslic3r/PrintConfig.cpp:1803 +msgid "°" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:100 +msgid "Bridges fan speed" +msgstr "브릿지 팬 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:101 +msgid "This fan speed is enforced during all bridges and overhangs." +msgstr "이 팬 속도는 모든 브릿지 및 오버행 중에 적용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:102 xs/src/libslic3r/PrintConfig.cpp:601 +#: xs/src/libslic3r/PrintConfig.cpp:990 xs/src/libslic3r/PrintConfig.cpp:1058 +#: xs/src/libslic3r/PrintConfig.cpp:1308 +msgid "%" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:109 +msgid "Bridge flow ratio" +msgstr "브릿지 유량(flow)값" + +#: xs/src/libslic3r/PrintConfig.cpp:111 +msgid "This factor affects the amount of plastic for bridging. You can decrease it slightly to pull the extrudates and prevent sagging, although default settings are usually good and you should experiment with cooling (use a fan) before tweaking this." +msgstr "이 요인은 브리징을위한 플라스틱의 양에 영향을 미칩니다. 압출 성형물을 잡아 당겨 처짐을 방지하기 위해 약간 줄일 수 있지만 기본 설정은 일반적으로 좋지만이 문제를 해결하기 전에 냉각 (팬 사용)을 시도해야합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:121 +msgid "Bridges" +msgstr "브릿지(Bridges)" + +#: xs/src/libslic3r/PrintConfig.cpp:123 +msgid "Speed for printing bridges." +msgstr "브릿지 인쇄 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:124 xs/src/libslic3r/PrintConfig.cpp:471 +#: xs/src/libslic3r/PrintConfig.cpp:480 xs/src/libslic3r/PrintConfig.cpp:508 +#: xs/src/libslic3r/PrintConfig.cpp:516 xs/src/libslic3r/PrintConfig.cpp:735 +#: xs/src/libslic3r/PrintConfig.cpp:846 xs/src/libslic3r/PrintConfig.cpp:922 +#: xs/src/libslic3r/PrintConfig.cpp:940 xs/src/libslic3r/PrintConfig.cpp:952 +#: xs/src/libslic3r/PrintConfig.cpp:962 xs/src/libslic3r/PrintConfig.cpp:1019 +#: xs/src/libslic3r/PrintConfig.cpp:1076 xs/src/libslic3r/PrintConfig.cpp:1214 +#: xs/src/libslic3r/PrintConfig.cpp:1385 xs/src/libslic3r/PrintConfig.cpp:1394 +#: xs/src/libslic3r/PrintConfig.cpp:1782 xs/src/libslic3r/PrintConfig.cpp:1895 +msgid "mm/s" +msgstr "mm/s" + +#: xs/src/libslic3r/PrintConfig.cpp:131 +msgid "Brim width" +msgstr "브림 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:132 +msgid "Horizontal width of the brim that will be printed around each object on the first layer." +msgstr "첫 번째 레이어의 각 객체 주위에 인쇄 될 가장자리의 가로 폭입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:139 +msgid "Clip multi-part objects" +msgstr "여러 파트 오브젝트 클립" + +#: xs/src/libslic3r/PrintConfig.cpp:140 +msgid "When printing multi-material objects, this settings will make slic3r to clip the overlapping object parts one by the other (2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)." +msgstr "멀티 메터리얼(multi-material) 개체를 인쇄 할 때이 설정을 사용하면 겹치는 개체 파트를 서로 겹쳐서 잘라낼 수 있습니다 (두 번째 부분은 첫 번째 부분에서 클리핑되며 세 번째 부분은 첫 번째 및 두 번째 부분에서 잘립니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:151 +msgid "Compatible printers condition" +msgstr "호환 가능한 프린터 조건" + +#: xs/src/libslic3r/PrintConfig.cpp:152 +msgid "A boolean expression using the configuration values of an active printer profile. If this expression evaluates to true, this profile is considered compatible with the active printer profile." +msgstr "활성 프린터 프로파일의 구성 값을 사용하는 부울 표현식. 이 표현식이 true로 평가되면이 프로필은 활성 프린터 프로필과 호환되는 것으로 간주됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:163 +msgid "Complete individual objects" +msgstr "개별 개체 완성" + +#: xs/src/libslic3r/PrintConfig.cpp:164 +msgid "When printing multiple objects or copies, this feature will complete each object before moving onto next one (and starting it from its bottom layer). This feature is useful to avoid the risk of ruined prints. Slic3r should warn and prevent you from extruder collisions, but beware." +msgstr "여러 객체 또는 사본을 인쇄 할 때이 객체는 다음 객체로 이동하기 전에 각 객체를 완성합니다 (맨 아래 레이어에서 시작). 이 기능은 인쇄물이 망가지는 위험을 피할 때 유용합니다. Slic3r은 압출기 충돌을 경고하고 예방해야하지만 조심하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:172 +msgid "Enable auto cooling" +msgstr "자동 냉각 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:173 +msgid "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time." +msgstr "이 플래그는 레이어 인쇄 시간에 따라 인쇄 속도와 팬 속도를 조정하는 자동 냉각 논리를 활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:179 +msgid "Cooling tube position" +msgstr "냉각 튜브 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:180 +msgid "Distance of the center-point of the cooling tube from the extruder tip " +msgstr "압출기 팁에서 냉각 튜브의 중심점까지의 거리 " + +#: xs/src/libslic3r/PrintConfig.cpp:187 +msgid "Cooling tube length" +msgstr "냉각 튜브 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:188 +msgid "Length of the cooling tube to limit space for cooling moves inside it " +msgstr "내부의 냉각 이동을 위해 공간을 제한하는 냉각 튜브의 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:196 +msgid "This is the acceleration your printer will be reset to after the role-specific acceleration values are used (perimeter/infill). Set zero to prevent resetting acceleration at all." +msgstr "역할 별 가속도 값이 사용 된 후에 프린터가 재설정되는 속도입니다 (둘레 / 충전). 가속을 전혀 재설정하지 않으려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:205 +msgid "Default filament profile" +msgstr "기본 필라멘트 프로파일" + +#: xs/src/libslic3r/PrintConfig.cpp:206 +msgid "Default filament profile associated with the current printer profile. On selection of the current printer profile, this filament profile will be activated." +msgstr "현재 프린터 프로파일과 연관된 기본 필라멘트 프로파일. 현재 프린터 프로파일을 선택하면 이 필라멘트 프로파일이 활성화됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:211 +msgid "Default print profile" +msgstr "기본 인쇄 프로파일" + +#: xs/src/libslic3r/PrintConfig.cpp:212 +msgid "Default print profile associated with the current printer profile. On selection of the current printer profile, this print profile will be activated." +msgstr "현재 프린터 프로파일과 연관된 기본 인쇄 프로파일. 현재 프린터 프로파일을 선택하면이 인쇄 프로파일이 활성화됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:217 +msgid "Disable fan for the first" +msgstr "첫 번째 팬 사용 중지" + +#: xs/src/libslic3r/PrintConfig.cpp:218 +msgid "You can set this to a positive value to disable fan at all during the first layers, so that it does not make adhesion worse." +msgstr "이 값을 양수 값으로 설정하면 첫 번째 레이어에서 팬을 사용하지 않도록 설정하여 접착력을 악화시키지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:220 xs/src/libslic3r/PrintConfig.cpp:791 +#: xs/src/libslic3r/PrintConfig.cpp:1281 xs/src/libslic3r/PrintConfig.cpp:1472 +#: xs/src/libslic3r/PrintConfig.cpp:1533 xs/src/libslic3r/PrintConfig.cpp:1685 +#: xs/src/libslic3r/PrintConfig.cpp:1730 +msgid "layers" +msgstr "레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:227 +msgid "Don't support bridges" +msgstr "서포트와 브릿지를 사용하지 마세요." + +#: xs/src/libslic3r/PrintConfig.cpp:229 +msgid "Experimental option for preventing support material from being generated under bridged areas." +msgstr "브릿지 영역 아래에 서포팅 재료가 생성되는 것을 방지하기위한 실험적 옵션." + +#: xs/src/libslic3r/PrintConfig.cpp:235 +msgid "Distance between copies" +msgstr "복사본 간 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:236 +msgid "Distance used for the auto-arrange feature of the plater." +msgstr "플래터(plater)의 자동 정렬 기능에 사용되는 거리입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:244 +msgid "Elephant foot compensation" +msgstr "코끼리 발(Elephant foot) 보상값" + +#: xs/src/libslic3r/PrintConfig.cpp:246 +msgid "The first layer will be shrunk in the XY plane by the configured value to compensate for the 1st layer squish aka an Elephant Foot effect." +msgstr "첫 번째 레이어는 구성 요소 값에 따라 XY 평면에서 수축되어 일층 스 퀴시 코끼리발(Elephant Foot) 효과를 보완합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:255 +msgid "This end procedure is inserted at the end of the output file. Note that you can use placeholder variables for all Slic3r settings." +msgstr "이 종료 절차는 출력 파일의 끝에 삽입된다. 모든 Slic3r 설정에 자리 표시자 변수를 사용할 수 있다는 점에 유의하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:265 +msgid "This end procedure is inserted at the end of the output file, before the printer end gcode. Note that you can use placeholder variables for all Slic3r settings. If you have multiple extruders, the gcode is processed in extruder order." +msgstr "이 종료 절차는 출력 파일의 끝에 프린터 끝 코드 앞에 삽입된다. 모든 Slic3r 설정에 자리 표시자 변수를 사용할 수 있다는 점에 유의하십시오. 여러 개의 압출부가 있는 경우, 그 코드는 압출 순서대로 처리된다." + +#: xs/src/libslic3r/PrintConfig.cpp:275 +msgid "Ensure vertical shell thickness" +msgstr "수직 쉘(shell) 두께 확인" + +#: xs/src/libslic3r/PrintConfig.cpp:277 +msgid "Add solid infill near sloping surfaces to guarantee the vertical shell thickness (top+bottom solid layers)." +msgstr "경사 표면 근처에 솔리드 인필을 추가하여 수직 셸 두께(상단+하단 솔리드 레이어)를 보장하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:283 +msgid "Top/bottom fill pattern" +msgstr "상단/하단 채우기(fill) 패턴" + +#: xs/src/libslic3r/PrintConfig.cpp:285 +msgid "Fill pattern for top/bottom infill. This only affects the external visible layer, and not its adjacent solid shells." +msgstr "상단/하단 주입의 채우기 패턴은 인접한 외부면이 아닌 외부 솔리드 층에만 영향을 미친다." + +#: xs/src/libslic3r/PrintConfig.cpp:294 xs/src/libslic3r/PrintConfig.cpp:654 +#: xs/src/libslic3r/PrintConfig.cpp:1764 +msgid "Rectilinear" +msgstr "직선면(Rectilinear)" + +#: xs/src/libslic3r/PrintConfig.cpp:295 xs/src/libslic3r/PrintConfig.cpp:660 +msgid "Concentric" +msgstr "동심원(Concentric)" + +#: xs/src/libslic3r/PrintConfig.cpp:296 xs/src/libslic3r/PrintConfig.cpp:664 +msgid "Hilbert Curve" +msgstr "힐버트 곡선(Hilbert Curve)" + +#: xs/src/libslic3r/PrintConfig.cpp:297 xs/src/libslic3r/PrintConfig.cpp:665 +msgid "Archimedean Chords" +msgstr "아르키메데우스(Archimedean Chords)" + +#: xs/src/libslic3r/PrintConfig.cpp:298 xs/src/libslic3r/PrintConfig.cpp:666 +msgid "Octagram Spiral" +msgstr "옥타그램 나선(Octagram Spiral)" + +#: xs/src/libslic3r/PrintConfig.cpp:304 xs/src/libslic3r/PrintConfig.cpp:314 +msgid "External perimeters" +msgstr "외측 둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:305 xs/src/libslic3r/PrintConfig.cpp:415 +#: xs/src/libslic3r/PrintConfig.cpp:689 xs/src/libslic3r/PrintConfig.cpp:807 +#: xs/src/libslic3r/PrintConfig.cpp:1200 xs/src/libslic3r/PrintConfig.cpp:1540 +#: xs/src/libslic3r/PrintConfig.cpp:1702 xs/src/libslic3r/PrintConfig.cpp:1860 +msgid "Extrusion Width" +msgstr "압출 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:306 +msgid "Set this to a non-zero value to set a manual extrusion width for external perimeters. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. If expressed as percentage (for example 200%), it will be computed over layer height." +msgstr "외부 경계에 대한 수동 압출 폭을 설정하려면 이 값을 0이 아닌 값으로 설정하십시오. 0인 경우 기본 압출 너비가 사용되며, 그렇지 않으면 1.125 x 노즐 직경이 사용된다. 백분율(예: 200%)로 표현되는 경우, 레이어 높이에 걸쳐 계산된다." + +#: xs/src/libslic3r/PrintConfig.cpp:309 xs/src/libslic3r/PrintConfig.cpp:694 +#: xs/src/libslic3r/PrintConfig.cpp:812 xs/src/libslic3r/PrintConfig.cpp:1205 +#: xs/src/libslic3r/PrintConfig.cpp:1544 xs/src/libslic3r/PrintConfig.cpp:1706 +#: xs/src/libslic3r/PrintConfig.cpp:1865 +msgid "mm or % (leave 0 for default)" +msgstr "mm 또는 %(기본값의 경우 0으로 유지)" + +#: xs/src/libslic3r/PrintConfig.cpp:316 +msgid "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto." +msgstr "이 별도의 설정은 외부 경계선(시각적 경계선)의 속도에 영향을 미친다. 백분율(예: 80%)로 표현되는 경우 위의 Perimeter 속도 설정에 따라 계산된다. 자동을 위해 0으로 설정한다." + +#: xs/src/libslic3r/PrintConfig.cpp:319 xs/src/libslic3r/PrintConfig.cpp:716 +#: xs/src/libslic3r/PrintConfig.cpp:1503 xs/src/libslic3r/PrintConfig.cpp:1554 +#: xs/src/libslic3r/PrintConfig.cpp:1749 xs/src/libslic3r/PrintConfig.cpp:1877 +msgid "mm/s or %" +msgstr "mm/s 또는 %" + +#: xs/src/libslic3r/PrintConfig.cpp:326 +msgid "External perimeters first" +msgstr "외부 경계선 먼저" + +#: xs/src/libslic3r/PrintConfig.cpp:328 +msgid "Print contour perimeters from the outermost one to the innermost one instead of the default inverse order." +msgstr "기본 역순 대신 가장 바깥쪽부터 가장 안쪽까지 윤곽선을 인쇄하십시오. 타겟 TTS복사하기번역 저장번역 저장번역 수정." + +#: xs/src/libslic3r/PrintConfig.cpp:334 +msgid "Extra perimeters if needed" +msgstr "필요한 경우 추가 둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:336 +#, no-c-format +msgid "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported." +msgstr "경사 벽의 틈을 피하기 위해 필요한 경우 더 많은 perimeter를 추가하십시오. 위의 루프의 70% 이상이 지지될 때까지 Slic3r는 계속해서 perimeter를 추가한다." + +#: xs/src/libslic3r/PrintConfig.cpp:346 +msgid "The extruder to use (unless more specific extruder settings are specified). This value overrides perimeter and infill extruders, but not the support extruders." +msgstr "사용할 압출부(더 구체적인 압출부 설정이 지정되지 않은 경우) 이 값은 경계 및 압출부를 초과하지만 지원 압출자를 주입하지는 않는다." + +#: xs/src/libslic3r/PrintConfig.cpp:358 lib/Slic3r/GUI/Plater/3DPreview.pm:75 +msgid "Height" +msgstr "높이" + +#: xs/src/libslic3r/PrintConfig.cpp:359 +msgid "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. In other words, this is the height of the clearance cylinder around your extruder, and it represents the maximum depth the extruder can peek before colliding with other printed objects." +msgstr "이것을 노즐 팁과 (일반적으로) X 캐리지 로드 사이의 수직 거리로 설정하십시오. 다시 말하면, 이것은 당신의 압출기 주위의 틈새 실린더의 높이이며, 그것은 다른 인쇄된 물체와 충돌하기 전에 압출기가 엿볼 수 있는 최대 깊이를 나타낸다." + +#: xs/src/libslic3r/PrintConfig.cpp:369 +msgid "Radius" +msgstr "반지름" + +#: xs/src/libslic3r/PrintConfig.cpp:370 +msgid "Set this to the clearance radius around your extruder. If the extruder is not centered, choose the largest value for safety. This setting is used to check for collisions and to display the graphical preview in the plater." +msgstr "이것을 당신의 압출기 주변의 간극 반경으로 설정하시오. 압출부가 중앙에 있지 않으면 안전을 위해 가장 큰 값을 선택하십시오. 이 설정은 충돌 여부를 확인하고 플래터에 그래픽 미리 보기를 표시하기 위해 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:380 +msgid "Extruder Color" +msgstr "익스트루더 컬러" + +#: xs/src/libslic3r/PrintConfig.cpp:381 xs/src/libslic3r/PrintConfig.cpp:444 +msgid "This is only used in the Slic3r interface as a visual help." +msgstr "이것은 시각적 도움말로 Slic3r 인터페이스에서만 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:388 +msgid "Extruder offset" +msgstr "익스트루더 오프셋" + +#: xs/src/libslic3r/PrintConfig.cpp:389 +msgid "If your firmware doesn't handle the extruder displacement you need the G-code to take it into account. This option lets you specify the displacement of each extruder with respect to the first one. It expects positive coordinates (they will be subtracted from the XY coordinate)." +msgstr "펌웨어가 압출기 위치 변경을 처리하지 못하면 G 코드를 고려해야합니다. 이 옵션을 사용하면 첫 번째 것에 대한 각 압출기의 변위를 지정할 수 있습니다. 양의 좌표가 필요합니다 (XY 좌표에서 뺍니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:398 +msgid "Extrusion axis" +msgstr "압출 축" + +#: xs/src/libslic3r/PrintConfig.cpp:399 +msgid "Use this option to set the axis letter associated to your printer's extruder (usually E but some printers use A)." +msgstr "이 옵션을 사용하여 프린터의 압출기에 연결된 축 문자를 설정합니다 (보통 E이지만 일부 프린터는 A를 사용합니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:405 +msgid "Extrusion multiplier" +msgstr "압출 승수" + +#: xs/src/libslic3r/PrintConfig.cpp:406 +msgid "This factor changes the amount of flow proportionally. You may need to tweak this setting to get nice surface finish and correct single wall widths. Usual values are between 0.9 and 1.1. If you think you need to change this more, check filament diameter and your firmware E steps." +msgstr "이 요소는 비례하여 유량의 양을 변경합니다. 멋진 서페이스 마무리와 단일 벽 너비를 얻기 위해이 설정을 조정해야 할 수도 있습니다. 일반적인 값은 0.9와 1.1 사이입니다. 이 값을 더 변경해야한다고 판단되면 필라멘트 직경과 펌웨어 E 단계를 확인하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:414 +msgid "Default extrusion width" +msgstr "기본 압출 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:416 +msgid "Set this to a non-zero value to allow a manual extrusion width. If left to zero, Slic3r derives extrusion widths from the nozzle diameter (see the tooltips for perimeter extrusion width, infill extrusion width etc). If expressed as percentage (for example: 230%), it will be computed over layer height." +msgstr "수동 압출 폭을 허용하려면이 값을 0이 아닌 값으로 설정하십시오. 0으로 남겨두면 Slic3r은 노즐 직경에서 압출 폭을 도출합니다 (주변 압출 폭, 성형 압출 폭 등의 툴팁 참조). 백분율로 표시되는 경우 (예 : 230 %) 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:420 +msgid "mm or % (leave 0 for auto)" +msgstr "mm 또는 % (자동으로 0을 유지)" + +#: xs/src/libslic3r/PrintConfig.cpp:425 +msgid "Keep fan always on" +msgstr "항상 팬 켜기" + +#: xs/src/libslic3r/PrintConfig.cpp:426 +msgid "If this is enabled, fan will never be disabled and will be kept running at least at its minimum speed. Useful for PLA, harmful for ABS." +msgstr "이 기능을 사용하면 팬이 비활성화되지 않으며 최소한 최소 속도로 계속 회전합니다. PLA에 유용하며 ABS에 해롭다." + +#: xs/src/libslic3r/PrintConfig.cpp:432 +msgid "Enable fan if layer print time is below" +msgstr "레이어 인쇄 시간이 미만인 경우 팬 활성화" + +#: xs/src/libslic3r/PrintConfig.cpp:433 +msgid "If layer print time is estimated below this number of seconds, fan will be enabled and its speed will be calculated by interpolating the minimum and maximum speeds." +msgstr "레이어 인쇄 시간이이 초 미만으로 예상되는 경우 팬이 활성화되고 속도는 최소 및 최대 속도를 보간하여 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:435 xs/src/libslic3r/PrintConfig.cpp:1490 +msgid "approximate seconds" +msgstr "근사치 초" + +#: xs/src/libslic3r/PrintConfig.cpp:443 +msgid "Color" +msgstr "색상" + +#: xs/src/libslic3r/PrintConfig.cpp:450 +msgid "Filament notes" +msgstr "필라멘트 메모" + +#: xs/src/libslic3r/PrintConfig.cpp:451 +msgid "You can put your notes regarding the filament here." +msgstr "여기에 필라멘트에 관한 메모를 넣을 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:459 xs/src/libslic3r/PrintConfig.cpp:1025 +msgid "Max volumetric speed" +msgstr "최대 체적 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:460 +msgid "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric speed of a print to the minimum of print and filament volumetric speed. Set to zero for no limit." +msgstr "이 필라멘트에 허용되는 최대 체적 속도. 인쇄물의 최대 체적 속도를 인쇄 및 필라멘트 체적 속도 최소로 제한한다. 제한 없음에 대해 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:463 xs/src/libslic3r/PrintConfig.cpp:1028 +msgid "mm³/s" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:469 +msgid "Loading speed" +msgstr "로딩 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:470 +msgid "Speed used for loading the filament on the wipe tower. " +msgstr "와이퍼 탑(wipe)에 필라멘트를 장착하는 데 사용되는 속도. " + +#: xs/src/libslic3r/PrintConfig.cpp:477 +msgid "Unloading speed" +msgstr "언로딩 스피드" + +#: xs/src/libslic3r/PrintConfig.cpp:478 +msgid "Speed used for unloading the filament on the wipe tower (does not affect initial part of unloading just after ramming). " +msgstr "와이퍼 타워에서 필라멘트를 언로드하는 데 사용되는 속도(램핑 후 바로 언로딩의 초기 부분에는 영향을 주지 않음)" + +#: xs/src/libslic3r/PrintConfig.cpp:486 +msgid "Delay after unloading" +msgstr "언로드 후 딜레이" + +#: xs/src/libslic3r/PrintConfig.cpp:487 +msgid "Time to wait after the filament is unloaded. May help to get reliable toolchanges with flexible materials that may need more time to shrink to original dimensions. " +msgstr "필라멘트를 내린 후 기다리는 시간. 원래 치수로 축소하는 데 더 많은 시간이 필요할 수있는 유연한 재료로 신뢰할 수있는 공구 교환을 얻을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:496 +msgid "Number of cooling moves" +msgstr "쿨링 이동 숫자" + +#: xs/src/libslic3r/PrintConfig.cpp:497 +msgid "Filament is cooled by being moved back and forth in the cooling tubes. Specify desired number of these moves " +msgstr "필라멘트는 냉각 튜브에서 앞뒤로 움직여 냉각됩니다. 원하는 이동 숫자 지정" + +#: xs/src/libslic3r/PrintConfig.cpp:505 +msgid "Speed of the first cooling move" +msgstr "첫 번째 냉각 이동 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:506 +msgid "Cooling moves are gradually accelerating beginning at this speed. " +msgstr "냉각 속도가 서서히 빨라지고 있습니다. " + +#: xs/src/libslic3r/PrintConfig.cpp:513 +msgid "Speed of the last cooling move" +msgstr "마지막 냉각 이동 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:514 +msgid "Cooling moves are gradually accelerating towards this speed. " +msgstr "냉각은 이 속도쪽으로 점차 가속화되고 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:521 +msgid "Ramming parameters" +msgstr "래밍 파라미터" + +#: xs/src/libslic3r/PrintConfig.cpp:522 +msgid "This string is edited by RammingDialog and contains ramming specific parameters " +msgstr "이 문자열은 RammingDialog에 의해 편집되고 램밍 특정 매개 변수를 포함합니다" + +#: xs/src/libslic3r/PrintConfig.cpp:529 +msgid "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average." +msgstr "여기에 필라멘트 직경을 입력하십시오. 정밀도가 필요하므로 캘리퍼를 사용하여 필라멘트를 따라 여러 번 측정 한 다음 평균을 계산하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:537 +msgid "Density" +msgstr "밀도" + +#: xs/src/libslic3r/PrintConfig.cpp:538 +msgid "Enter your filament density here. This is only for statistical information. A decent way is to weigh a known length of filament and compute the ratio of the length to volume. Better is to calculate the volume directly through displacement." +msgstr "여기서 필라멘트 밀도를 입력하십시오. 이것은 통계 정보 용입니다. 괜찮은 방법은 알려진 길이의 필라멘트의 무게를 측정하고 길이와 볼륨의 비율을 계산하는 것입니다. 변위를 통해 직접적으로 부피를 계산하는 것이 더 좋습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:541 +msgid "g/cm³" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:547 +msgid "Filament type" +msgstr "필라멘트 타입" + +#: xs/src/libslic3r/PrintConfig.cpp:548 xs/src/libslic3r/PrintConfig.cpp:1235 +msgid "If you want to process the output G-code through custom scripts, just list their absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed the absolute path to the G-code file as the first argument, and they can access the Slic3r config settings by reading environment variables." +msgstr "사용자 정의 스크립트를 통해 출력 G 코드를 처리하려면 여기에 절대 경로를 나열하십시오. 여러 개의 스크립트를 세미콜론으로 구분하십시오. 스크립트는 G 코드 파일의 절대 경로를 첫 번째 인수로 전달되며 환경 변수를 읽음으로써 Slic3r 구성 설정에 액세스 할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:567 +msgid "Soluble material" +msgstr "수용성 재료" + +#: xs/src/libslic3r/PrintConfig.cpp:568 +msgid "Soluble material is most likely used for a soluble support." +msgstr "수용성 재료눈 물에 녹는 서포트에 가장 많이 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:573 lib/Slic3r/GUI/Plater.pm:1616 +msgid "Cost" +msgstr "비용" + +#: xs/src/libslic3r/PrintConfig.cpp:574 +msgid "Enter your filament cost per kg here. This is only for statistical information." +msgstr "kg 당 필라멘트 비용을 여기에 입력하십시오. 통계를 내기 위해서 입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:575 +msgid "money/kg" +msgstr "원(\\)/kg" + +#: xs/src/libslic3r/PrintConfig.cpp:584 +msgid "Fill angle" +msgstr "채움 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:586 +msgid "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them." +msgstr "본 오리엔테이션 방향의 기본 각도입니다. 해칭이 적용될 것입니다. Slic3r이 감지 할 수있는 최상의 방향을 사용하여 브릿징이 채워지므로이 설정은 영향을 미치지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:598 +msgid "Fill density" +msgstr "채우기(fill) 밀도" + +#: xs/src/libslic3r/PrintConfig.cpp:600 +#, no-c-format +msgid "Density of internal infill, expressed in the range 0% - 100%." +msgstr "0 % - 100 % 범위로 표현 된 내부 채움(infill)의 밀도." + +#: xs/src/libslic3r/PrintConfig.cpp:636 +msgid "Fill pattern" +msgstr "채우기(fill) 패턴" + +#: xs/src/libslic3r/PrintConfig.cpp:638 +msgid "Fill pattern for general low-density infill." +msgstr "일반 낮은 밀도 채움의 패턴." + +#: xs/src/libslic3r/PrintConfig.cpp:655 +msgid "Grid" +msgstr "그리드(Grid)" + +#: xs/src/libslic3r/PrintConfig.cpp:656 +msgid "Triangles" +msgstr "삼각형(Triangles)" + +#: xs/src/libslic3r/PrintConfig.cpp:657 +msgid "Stars" +msgstr "별(Stars)" + +#: xs/src/libslic3r/PrintConfig.cpp:658 +msgid "Cubic" +msgstr "큐빅" + +#: xs/src/libslic3r/PrintConfig.cpp:659 +msgid "Line" +msgstr "선(Line)" + +#: xs/src/libslic3r/PrintConfig.cpp:661 xs/src/libslic3r/PrintConfig.cpp:1766 +msgid "Honeycomb" +msgstr "벌집" + +#: xs/src/libslic3r/PrintConfig.cpp:662 +msgid "3D Honeycomb" +msgstr "3D 벌집" + +#: xs/src/libslic3r/PrintConfig.cpp:663 +msgid "Gyroid" +msgstr "자이로이드(Gyroid)" + +#: xs/src/libslic3r/PrintConfig.cpp:670 xs/src/libslic3r/PrintConfig.cpp:679 +#: xs/src/libslic3r/PrintConfig.cpp:688 xs/src/libslic3r/PrintConfig.cpp:722 +msgid "First layer" +msgstr "첫 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:671 +msgid "This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer." +msgstr "이것은 프린터가 첫 번째 레이어에 사용할 가속도입니다. 0을 설정하면 첫 번째 레이어에 대한 가속 제어가 사용되지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:680 +msgid "Heated build plate temperature for the first layer. Set this to zero to disable bed temperature control commands in the output." +msgstr "첫 번째 레이어에 대한 빌드 플레이트 온도를 가열. 이 값을 0으로 설정하면 출력에서 ​​베드 온도 제어 명령을 비활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:690 +msgid "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) it will be computed over first layer height. If set to zero, it will use the default extrusion width." +msgstr "첫 번째 레이어의 수동 압출 폭을 설정하려면이 값을 0이 아닌 값으로 설정합니다. 이 방법을 사용하면보다 우수한 접착력을 위해 더 두꺼운 압출 성형물을 만들 수 있습니다. 백분율 (예 : 120 %)로 표현하면 첫 번째 레이어 높이를 기준으로 계산됩니다. 0으로 설정하면 기본 압출 폭이 사용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:700 +msgid "First layer height" +msgstr "첫 레이어 높이" + +#: xs/src/libslic3r/PrintConfig.cpp:702 +msgid "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height." +msgstr "매우 낮은 층의 높이로 인쇄할 때, 당신은 여전히 완벽하지 않은 빌드 플레이트의 부착력과 허용오차를 개선하기 위해 더 두꺼운 바닥 층을 인쇄하기를 원할 수 있다. 이것은 절대값 또는 기본 계층 높이에 대한 백분율(예: 150%)로 표시할 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:706 xs/src/libslic3r/PrintConfig.cpp:837 +#: xs/src/libslic3r/PrintConfig.cpp:1638 +msgid "mm or %" +msgstr "mm/s 또는 %" + +#: xs/src/libslic3r/PrintConfig.cpp:712 +msgid "First layer speed" +msgstr "첫 레이어 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:713 +msgid "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds." +msgstr "절대값(mm/s)으로 표현되는 경우, 이 속도는 유형에 관계없이 첫 번째 층의 모든 인쇄 이동에 적용된다. 백분율(예: 40%)로 표현되는 경우 기본 속도를 스케일링한다." + +#: xs/src/libslic3r/PrintConfig.cpp:723 +msgid "Extruder temperature for first layer. If you want to control temperature manually during print, set this to zero to disable temperature control commands in the output file." +msgstr "첫 번째 층의 외부 온도. 인쇄 중에 온도를 수동으로 제어하려면 출력 파일에서 온도 제어 명령을 사용하지 않으려면 이 값을 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:731 +#: xs/src/libslic3r/GCode/PreviewData.cpp:170 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:97 +msgid "Gap fill" +msgstr "공백 채움" + +#: xs/src/libslic3r/PrintConfig.cpp:733 +msgid "Speed for filling small gaps using short zigzag moves. Keep this reasonably low to avoid too much shaking and resonance issues. Set zero to disable gaps filling." +msgstr "짧은 지그재그로 작은 틈을 메우기 위한 속도. 너무 많은 진동과 공진 문제를 피하기 위해 이것을 합리적으로 낮게 유지한다. 간격 채우기를 사용하지 않으려면 0을 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:741 +msgid "Verbose G-code" +msgstr "세부 G-코드" + +#: xs/src/libslic3r/PrintConfig.cpp:742 +msgid "Enable this to get a commented G-code file, with each line explained by a descriptive text. If you print from SD card, the additional weight of the file could make your firmware slow down." +msgstr "설명 텍스트로 설명되는 각 행과 함께 코멘트된 G-code 파일을 가져오려면 이 옵션을 선택하십시오. 만일 당신이 SD카드로 인쇄한다면, 파일의 추가 무게로 인해 펌웨어의 속도가 느려질 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:749 +msgid "G-code flavor" +msgstr "G-code 형식" + +#: xs/src/libslic3r/PrintConfig.cpp:750 +msgid "Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer's firmware to get a compatible output. The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all." +msgstr "온도 조절 등을 포함한 일부 G/M-코드 명령은 보편적이지 않다. 호환되는 출력을 얻으려면 이 옵션을 프린터의 펌웨어로 설정하십시오. \"압출 없음\" 형식은 Slic3r가 어떠한 압출 값도 출력하지 못하게 한다." + +#: xs/src/libslic3r/PrintConfig.cpp:774 +msgid "No extrusion" +msgstr "압출 없음" + +#: xs/src/libslic3r/PrintConfig.cpp:779 +msgid "This is the acceleration your printer will use for infill. Set zero to disable acceleration control for infill." +msgstr "이것은 당신 프린터의 채움 가속력이다. 주입에 대한 가속 제어를 비활성화하려면 0을 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:787 +msgid "Combine infill every" +msgstr "다음 시간마다 결합" + +#: xs/src/libslic3r/PrintConfig.cpp:789 +msgid "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy." +msgstr "이 기능은 인필을 결합하고 얇은 주변기기를 보존하면서 두꺼운 인필 층을 압출하여 인쇄 속도를 높일 수 있도록 하여 정확도를 높인다." + +#: xs/src/libslic3r/PrintConfig.cpp:793 +msgid "Combine infill every n layers" +msgstr "모든 n개 층을 채우기 위해 결합" + +#: xs/src/libslic3r/PrintConfig.cpp:798 +msgid "Infill extruder" +msgstr "채움(Infill) 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:800 +msgid "The extruder to use when printing infill." +msgstr "채움으로 사용할 익스트루더." + +#: xs/src/libslic3r/PrintConfig.cpp:808 +msgid "Set this to a non-zero value to set a manual extrusion width for infill. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "채움에 수동 압출 폭을 설정하려면이 값을 0이 아닌 값으로 설정합니다. 0으로 설정하면 설정된 경우 기본 압출 폭이 사용되고 그렇지 않으면 1.125 x 노즐 직경이 사용됩니다. 채움 속도를 높이고 부품을 더 강하게 만들려면보다 큰 압출 성형물을 사용하는 것이 좋습니다. 백분율 (예 : 90 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:817 +msgid "Infill before perimeters" +msgstr "둘레보다 앞쪽에 채움" + +#: xs/src/libslic3r/PrintConfig.cpp:818 +msgid "This option will switch the print order of perimeters and infill, making the latter first." +msgstr "이 옵션은 외부출력과 채움 인쇄 순서를 바꾸어, 후자를 먼저 만든다." + +#: xs/src/libslic3r/PrintConfig.cpp:823 +msgid "Only infill where needed" +msgstr "필요한 경우 채음" + +#: xs/src/libslic3r/PrintConfig.cpp:825 +msgid "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved." +msgstr "이 옵션은 천장 지원에 실제로 필요한 영역에만 적용된다(내부 서포트 재료 역할을 할 것이다). 활성화된 경우 관련된 여러 번의 점검으로 인해 G-code 생성 속도를 늦춰라." + +#: xs/src/libslic3r/PrintConfig.cpp:832 +msgid "Infill/perimeters overlap" +msgstr "채움/둘레 겹침(perimeters overlap)" + +#: xs/src/libslic3r/PrintConfig.cpp:834 +msgid "This setting applies an additional overlap between infill and perimeters for better bonding. Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed as percentage (example: 15%) it is calculated over perimeter extrusion width." +msgstr "이 설정은 더 나은 본딩을 위해 충전 및 둘레 사이에 추가 겹침을 적용합니다. 이론적으로 이것은 필요하지 않아야하지만 백래시가 갭을 유발할 수 있습니다. 백분율 (예 : 15 %)로 표시되는 경우 경계 압출 폭을 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:845 +msgid "Speed for printing the internal fill. Set to zero for auto." +msgstr "내부 채우기 인쇄 속도. 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:854 +msgid "Inherits profile" +msgstr "프로필 상속" + +#: xs/src/libslic3r/PrintConfig.cpp:855 +msgid "Name of the profile, from which this profile inherits." +msgstr "이 프로파일이 상속되는 프로파일의 이름." + +#: xs/src/libslic3r/PrintConfig.cpp:866 +msgid "Interface shells" +msgstr "인터페이스 셸(shells)" + +#: xs/src/libslic3r/PrintConfig.cpp:867 +msgid "Force the generation of solid shells between adjacent materials/volumes. Useful for multi-extruder prints with translucent materials or manual soluble support material." +msgstr "인접 재료/볼륨 사이에 고체 쉘 생성을 강제하십시오. 반투명 재료 또는 수동 수용성 서포트 재료를 사용한 다중 압ㅊ기 인쇄에 유용함." + +#: xs/src/libslic3r/PrintConfig.cpp:876 +msgid "This custom code is inserted at every layer change, right after the Z move and before the extruder moves to the first layer point. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]." +msgstr "이 사용자 정의 코드는 Z 이동 직후와 압출부가 첫 번째 레이어 포인트로 이동하기 전에 모든 레이어 변경 시 삽입된다. 모든 Slic3r 설정뿐만 아니라 [layer_num] 및 [layer_z]에 자리 표시자 변수를 사용할 수 있다는 점에 유의하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:888 +msgid "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print." +msgstr "이 설정은 슬라이스/레이어의 높이(따라서 총 수)를 제어한다. 얇은 층은 더 나은 정확성을 제공하지만 인쇄하는 데는 더 많은 시간이 걸린다." + +#: xs/src/libslic3r/PrintConfig.cpp:896 +msgid "Support silent mode" +msgstr "서포트 무음 모드" + +#: xs/src/libslic3r/PrintConfig.cpp:897 +msgid "Set silent mode for the G-code flavor" +msgstr "G-코드 특징에 대한 무음 모드 설정" + +#: xs/src/libslic3r/PrintConfig.cpp:919 +#, c-format +msgid "Maximum feedrate %1%" +msgstr "최 대 공 급 속 도" + +#: xs/src/libslic3r/PrintConfig.cpp:921 +#, c-format +msgid "Maximum feedrate of the %1% axis" +msgstr "최대 공급 속도 of the %1% axis" + +#: xs/src/libslic3r/PrintConfig.cpp:928 +#, c-format +msgid "Maximum acceleration %1%" +msgstr "최대가속 %1%" + +#: xs/src/libslic3r/PrintConfig.cpp:930 +#, c-format +msgid "Maximum acceleration of the %1% axis" +msgstr "최대 가속도는 %1% 축" + +#: xs/src/libslic3r/PrintConfig.cpp:937 +#, c-format +msgid "Maximum jerk %1%" +msgstr "최대 저크(jerk) %1%" + +#: xs/src/libslic3r/PrintConfig.cpp:939 +#, c-format +msgid "Maximum jerk of the %1% axis" +msgstr "최대 저크는(jerk) %1% axis" + +#: xs/src/libslic3r/PrintConfig.cpp:949 xs/src/libslic3r/PrintConfig.cpp:951 +msgid "Minimum feedrate when extruding" +msgstr "압출시 최소 공급 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:959 xs/src/libslic3r/PrintConfig.cpp:961 +msgid "Minimum travel feedrate" +msgstr "최소 이송 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:969 xs/src/libslic3r/PrintConfig.cpp:971 +msgid "Maximum acceleration when extruding" +msgstr "압출시 최대 가속도" + +#: xs/src/libslic3r/PrintConfig.cpp:979 xs/src/libslic3r/PrintConfig.cpp:981 +msgid "Maximum acceleration when retracting" +msgstr "리트렉션 최대 가속도" + +#: xs/src/libslic3r/PrintConfig.cpp:988 xs/src/libslic3r/PrintConfig.cpp:997 +msgid "Max" +msgstr "최대" + +#: xs/src/libslic3r/PrintConfig.cpp:989 +msgid "This setting represents the maximum speed of your fan." +msgstr "이 설정은 팬의 최대 속도를 나타냅니다." + +#: xs/src/libslic3r/PrintConfig.cpp:998 +#, no-c-format +msgid "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter." +msgstr "이것은이 익스트루더의 가장 높은 인쇄 가능 층 높이이며, 가변 층 높이 및 지지층 높이를 캡하는 데 사용됩니다. 합당한 층간 접착력을 얻기 위해 최대 권장 높이는 압출 폭의 75 %입니다. 0으로 설정하면 층 높이가 노즐 지름의 75 %로 제한됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1008 +msgid "Max print height" +msgstr "최대 프린트 높이" + +#: xs/src/libslic3r/PrintConfig.cpp:1009 +msgid "Set this to the maximum height that can be reached by your extruder while printing." +msgstr "인쇄 중에 익스트루더가 도달 할 수있는 최대 높이로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1015 +msgid "Max print speed" +msgstr "최대 프린트 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1016 +msgid "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow." +msgstr "다른 속도 설정을 0으로 설정할 경우, 지속적인 외부 압력을 유지하기 위해 최적의 속도를 자동 계산한다. 이 실험 설정은 허용할 최대 인쇄 속도를 설정하는 데 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1026 +msgid "This experimental setting is used to set the maximum volumetric speed your extruder supports." +msgstr "이 실험 설정은 압출기가 지원하는 최대 체적 속도를 설정하기 위해 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1034 +msgid "Max volumetric slope positive" +msgstr "최대 체적 기울기 양" + +#: xs/src/libslic3r/PrintConfig.cpp:1035 xs/src/libslic3r/PrintConfig.cpp:1046 +msgid "This experimental setting is used to limit the speed of change in extrusion rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds." +msgstr "이 실험 설정은 돌출율의 변화 속도를 제한하는데 사용된다. 1.8mm3/s2 값은 1.8mm3/s(0.45mm 압출 폭, 0.2mm 압출 높이, 공급 속도 20mm/s)에서 5.4mm3/s(공급 속도 60mm/s)로 변경하는 데 최소 2초 이상 걸린다." + +#: xs/src/libslic3r/PrintConfig.cpp:1039 xs/src/libslic3r/PrintConfig.cpp:1050 +msgid "mm³/s²" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1045 +msgid "Max volumetric slope negative" +msgstr "최대 체적 기울기 음수" + +#: xs/src/libslic3r/PrintConfig.cpp:1056 xs/src/libslic3r/PrintConfig.cpp:1065 +msgid "Min" +msgstr "최소" + +#: xs/src/libslic3r/PrintConfig.cpp:1057 +msgid "This setting represents the minimum PWM your fan needs to work." +msgstr "이 설정은 최소 PWM팬이 활동하는데 필요한를 나타냅니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1066 +msgid "This is the lowest printable layer height for this extruder and limits the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm." +msgstr "이것은 이 압출기에 대한 가장 낮은 인쇄 가능한 층 높이이고 가변 층 높이에 대한 분해능을 제한한다. 대표적인 값은 0.05mm와 0.1mm이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1074 +msgid "Min print speed" +msgstr "최소 인쇄 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1075 +msgid "Slic3r will not scale speed down below this speed." +msgstr "Slic3r는 이 속도 이하로 속도를 낮추지 않을 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1082 +msgid "Minimal filament extrusion length" +msgstr "최소 필라멘트 압출 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1083 +msgid "Generate no less than the number of skirt loops required to consume the specified amount of filament on the bottom layer. For multi-extruder machines, this minimum applies to each extruder." +msgstr "하단 레이어에서 지정된 양의 필라멘트를 사용하는 데 필요한 스커트 루프의 수 이상으로 생성한다. 멀티 익스트루더의 경우, 이 최소값은 각 추가기기에 적용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1092 +msgid "Configuration notes" +msgstr "구성 노트" + +#: xs/src/libslic3r/PrintConfig.cpp:1093 +msgid "You can put here your personal notes. This text will be added to the G-code header comments." +msgstr "여기에 개인 노트를 넣을 수 있다. 이 텍스트는 G-code 헤더 코멘트에 추가될 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1102 +msgid "Nozzle diameter" +msgstr "노즐 직경:" + +#: xs/src/libslic3r/PrintConfig.cpp:1103 +msgid "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)" +msgstr "이 지름은 엑스트루더 노즐의 직경이다(예: 0.5, 0.35 등)." + +#: xs/src/libslic3r/PrintConfig.cpp:1109 +msgid "API Key" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1110 +msgid "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication." +msgstr "Slic3r는 G-code 파일을 OctoPrint에 업로드할 수 있다. 이 필드에는 인증에 필요한 API 키가 포함되어야 한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1123 +msgid "Hostname, IP or URL" +msgstr "호스트 이름(Hostname), IP or URL" + +#: xs/src/libslic3r/PrintConfig.cpp:1124 +msgid "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname, IP address or URL of the OctoPrint instance." +msgstr "Slic3r는 G-code 파일을 OctoPrint에 업로드할 수 있다. 이 필드에는 OctoPrint 인스턴스의 호스트 이름, IP 주소 또는 URL이 포함되어야 한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1130 +msgid "Only retract when crossing perimeters" +msgstr "둘레를 횡단 할 때만 수축" + +#: xs/src/libslic3r/PrintConfig.cpp:1131 +msgid "Disables retraction when the travel path does not exceed the upper layer's perimeters (and thus any ooze will be probably invisible)." +msgstr "이동 경로가 상위 레이어의 경계를 초과하지 않는 경우 리트랙션을 비활성화합니다. 따라서 모든 오즈가 보이지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1138 +msgid "This option will drop the temperature of the inactive extruders to prevent oozing. It will enable a tall skirt automatically and move extruders outside such skirt when changing temperatures." +msgstr "이 옵션은 누출을 방지하기 위해 비활성 압출기의 온도를 떨어 뜨립니다. 온도를 변경할 때 키가 큰 스커트를 자동으로 사용하고 스커트 외부로 압출기를 이동합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1145 +msgid "Output filename format" +msgstr "출력 파일이름 형식" + +#: xs/src/libslic3r/PrintConfig.cpp:1146 +msgid "You can use all configuration options as variables inside this template. For example: [layer_height], [fill_density] etc. You can also use [timestamp], [year], [month], [day], [hour], [minute], [second], [version], [input_filename], [input_filename_base]." +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1155 +msgid "Detect bridging perimeters" +msgstr "브릿 징 경계선 감지" + +#: xs/src/libslic3r/PrintConfig.cpp:1157 +msgid "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan." +msgstr "오버행에 대한 유량을 조정하는 실험 옵션 (브리지 흐름(flow)이 사용됨)에 브릿지 속도를 적용하고 팬을 활성화합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1163 +msgid "Filament parking position" +msgstr "필라멘트 멈춤 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1164 +msgid "Distance of the extruder tip from the position where the filament is parked when unloaded. This should match the value in printer firmware. " +msgstr "언 로딩시 필라멘트 위치에서 압출기 팁의 거리. 이 값은 프린터 펌웨어의 값과 일치해야합니다" + +#: xs/src/libslic3r/PrintConfig.cpp:1172 +msgid "Extra loading distance" +msgstr "추가 로딩 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:1173 +msgid "When set to zero, the distance the filament is moved from parking position during load is exactly the same as it was moved back during unload. When positive, it is loaded further, if negative, the loading move is shorter than unloading. " +msgstr "0으로 설정하면로드 중에 필라멘트가 위치에서 이동 한 거리는 언로드 중에 다시 이동 한 거리와 동일합니다. 양수이면 음수가 더 많이 로드되고 로드가 음수 인 경우 언로드보다 짧습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1181 xs/src/libslic3r/PrintConfig.cpp:1199 +#: xs/src/libslic3r/PrintConfig.cpp:1211 xs/src/libslic3r/PrintConfig.cpp:1221 +msgid "Perimeters" +msgstr "둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:1182 +msgid "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters." +msgstr "프린터가 둘레로 사용할 가속도입니다. 9000과 같은 높은 값은 하드웨어가 제대로 작동하면 좋은 결과를 제공합니다. 주변을 가속 제어하지 않으려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1190 +msgid "Perimeter extruder" +msgstr "주변 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1192 +msgid "The extruder to use when printing perimeters and brim. First extruder is 1." +msgstr "둘레와 가장자리를 인쇄 할 때 사용할 압출기입니다. 첫 번째 압출기는 1입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1201 +msgid "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. If expressed as percentage (for example 200%) it will be computed over layer height." +msgstr "이 값을 0이 아닌 값으로 설정하면 수동 압출 폭을 둘레로 설정할 수 있습니다. 보다 정확한 서페이스를 얻으려면 더 얇은 압출 성형품을 사용하는 것이 좋습니다. 0으로 설정하면 설정된 경우 기본 돌출 폭이 사용되고 그렇지 않으면 1.125 x 노즐 직경이 사용됩니다. 백분율 (예 : 200 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1213 +msgid "Speed for perimeters (contours, aka vertical shells). Set to zero for auto." +msgstr "둘레의 속도 (등고선, 일명 세로 셸). 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1223 +msgid "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled." +msgstr "이 옵션은 각 레이어에 대해 생성 할 경계 수를 설정합니다. 추가 경계선 옵션을 사용하면 더 큰 주변 수를 사용하는 경사면을 감지 할 때 Slic3r이이 수를 자동으로 증가시킬 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1227 +msgid "(minimum)" +msgstr "(최소)" + +#: xs/src/libslic3r/PrintConfig.cpp:1247 +msgid "Printer type" +msgstr "프린터 타입" + +#: xs/src/libslic3r/PrintConfig.cpp:1248 +msgid "Type of the printer." +msgstr "프린터 유형." + +#: xs/src/libslic3r/PrintConfig.cpp:1252 +msgid "Printer notes" +msgstr "프린터 노트" + +#: xs/src/libslic3r/PrintConfig.cpp:1253 +msgid "You can put your notes regarding the printer here." +msgstr "프린터 관련 메모를 여기에 넣을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1261 +msgid "Printer vendor" +msgstr "제조 회사" + +#: xs/src/libslic3r/PrintConfig.cpp:1262 +msgid "Name of the printer vendor." +msgstr "프린터 공급 업체의 이름입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1266 +msgid "Printer variant" +msgstr "프린터 변형" + +#: xs/src/libslic3r/PrintConfig.cpp:1267 +msgid "Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter." +msgstr "프린터 변종 이름입니다. 예를 들어, 프린터 변형은 노즐 지름으로 구별 될 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1277 +msgid "Raft layers" +msgstr "라프트(Raft) 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:1279 +msgid "The object will be raised by this number of layers, and support material will be generated under it." +msgstr "물체는 이 개수의 층에 의해 상승되며, 그 아래에서 서포트 재료가 생성될 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1287 +msgid "Resolution" +msgstr "해결" + +#: xs/src/libslic3r/PrintConfig.cpp:1288 +msgid "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input." +msgstr "잘라내기 작업의 속도를 높이고 메모리 사용량을 줄이기 위해 입력 파일을 단순화하는 데 사용되는 최소 세부 해상도. 고해상도 모델은 종종 프린터가 렌더링할 수 있는 것보다 더 많은 디테일을 가지고 있다. 단순화를 사용하지 않고 입력에서 전체 해상도를 사용하려면 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1298 +msgid "Minimum travel after retraction" +msgstr "리트랙션 후 최소 이동 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:1299 +msgid "Retraction is not triggered when travel moves are shorter than this length." +msgstr "이동 거리가 이 길이보다 짧으면 리트렉션이 트리거되지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1305 +msgid "Retract amount before wipe" +msgstr "닦아 내기 전의 수축량" + +#: xs/src/libslic3r/PrintConfig.cpp:1306 +msgid "With bowden extruders, it may be wise to do some amount of quick retract before doing the wipe movement." +msgstr "보우 덴 압출기를 사용하면 와이퍼 동작을하기 전에 약간의 빠른 리트랙션 를하는 것이 좋습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1313 +msgid "Retract on layer change" +msgstr "레이어 변경 후퇴" + +#: xs/src/libslic3r/PrintConfig.cpp:1314 +msgid "This flag enforces a retraction whenever a Z move is done." +msgstr "이 플래그는 Z 이동이 완료 될 때마다 취소를 강제 실행합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1319 xs/src/libslic3r/PrintConfig.cpp:1328 +msgid "Length" +msgstr "길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1320 +msgid "Retraction Length" +msgstr "리트랙션 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1321 +msgid "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)." +msgstr "후퇴가 트리거되면 필라멘트가 지정된 양만큼 뒤로 당겨집니다 (길이는 압출기에 들어가기 전에 원시 필라멘트에서 측정됩니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:1323 xs/src/libslic3r/PrintConfig.cpp:1333 +msgid "mm (zero to disable)" +msgstr "mm (0은 비활성화)" + +#: xs/src/libslic3r/PrintConfig.cpp:1329 +msgid "Retraction Length (Toolchange)" +msgstr "리트랙션 길이 (툴 체인지)" + +#: xs/src/libslic3r/PrintConfig.cpp:1330 +msgid "When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)." +msgstr "공구를 교체하기 전에 후퇴가 트리거되면 필라멘트가 지정된 양만큼 뒤로 당겨집니다 (길이는 압출기에 들어가기 전에 원시 필라멘트에서 측정됩니다)." + +#: xs/src/libslic3r/PrintConfig.cpp:1338 +msgid "Lift Z" +msgstr "Z축 올림" + +#: xs/src/libslic3r/PrintConfig.cpp:1339 +msgid "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered." +msgstr "이 값을 양수 값으로 설정하면 철회가 트리거 될 때마다 Z가 빠르게 올라갑니다. 여러 개의 압출기를 사용하는 경우 첫 번째 압출기의 설정 만 고려됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1347 +msgid "Above Z" +msgstr "Z 위" + +#: xs/src/libslic3r/PrintConfig.cpp:1348 +msgid "Only lift Z above" +msgstr "오직 Z축 위로만" + +#: xs/src/libslic3r/PrintConfig.cpp:1349 +msgid "If you set this to a positive value, Z lift will only take place above the specified absolute Z. You can tune this setting for skipping lift on the first layers." +msgstr "이것을 양의 값으로 설정하면, Z 리프트는 지정된 절대 Z 위로만 발생한다. 첫 번째 층에서 리프트를 건너뛸 수 있도록 이 설정을 조정할 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:1356 +msgid "Below Z" +msgstr "Z 아래" + +#: xs/src/libslic3r/PrintConfig.cpp:1357 +msgid "Only lift Z below" +msgstr "Z값 아래만" + +#: xs/src/libslic3r/PrintConfig.cpp:1358 +msgid "If you set this to a positive value, Z lift will only take place below the specified absolute Z. You can tune this setting for limiting lift to the first layers." +msgstr "이것을 양수 값으로 설정하면 Z 리프트가 지정된 절대 Z 아래에서만 발생합니다. 첫 번째 레이어로 리프트를 제한하기 위해이 설정을 조정할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1366 xs/src/libslic3r/PrintConfig.cpp:1374 +msgid "Extra length on restart" +msgstr "재시작시 여분의 길이" + +#: xs/src/libslic3r/PrintConfig.cpp:1367 +msgid "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed." +msgstr "이동 후 리트렉셔이 보정되면 익스트루더가 추가 양의 필라멘트를 밀어냅니다. 이 설정은 거의 필요하지 않습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1375 +msgid "When the retraction is compensated after changing tool, the extruder will push this additional amount of filament." +msgstr "도구를 교환 한 후 리트렉션를 보정하면 익스트루더가 추가 양의 필라멘트를 밀게됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1382 xs/src/libslic3r/PrintConfig.cpp:1383 +msgid "Retraction Speed" +msgstr "리트랙션 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1384 +msgid "The speed for retractions (it only applies to the extruder motor)." +msgstr "리트랙션 속도 (익스트루더 모터에만 적용됨)." + +#: xs/src/libslic3r/PrintConfig.cpp:1390 xs/src/libslic3r/PrintConfig.cpp:1391 +msgid "Deretraction Speed" +msgstr "감속 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1392 +msgid "The speed for loading of a filament into extruder after retraction (it only applies to the extruder motor). If left to zero, the retraction speed is used." +msgstr "리트랙션 후 압출기에 필라멘트를 로드하는 속도 (압출기 모터에만 적용됨). 0으로 방치하면 리트랙션 속도가 사용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1399 +msgid "Seam position" +msgstr "재봉선 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1401 +msgid "Position of perimeters starting points." +msgstr "둘레의 시작점의 위치." + +#: xs/src/libslic3r/PrintConfig.cpp:1408 +msgid "Random" +msgstr "무작위" + +#: xs/src/libslic3r/PrintConfig.cpp:1409 +msgid "Nearest" +msgstr "가장 가까운" + +#: xs/src/libslic3r/PrintConfig.cpp:1410 +msgid "Aligned" +msgstr "정렬" + +#: xs/src/libslic3r/PrintConfig.cpp:1411 lib/Slic3r/GUI/MainFrame.pm:330 +msgid "Rear" +msgstr "뒷면" + +#: xs/src/libslic3r/PrintConfig.cpp:1417 +msgid "Direction" +msgstr "방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1419 +msgid "Preferred direction of the seam" +msgstr "선호하는 심(seam)의 방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1420 +msgid "Seam preferred direction" +msgstr "심(Seam) 선호 방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1428 +msgid "Jitter" +msgstr "지터(Jitter)" + +#: xs/src/libslic3r/PrintConfig.cpp:1430 +msgid "Seam preferred direction jitter" +msgstr "(Seam) 선호 방향 지터(Jitter)" + +#: xs/src/libslic3r/PrintConfig.cpp:1431 +msgid "Preferred direction of the seam - jitter" +msgstr "재봉선 지터의 선호 방향" + +#: xs/src/libslic3r/PrintConfig.cpp:1442 +msgid "USB/serial port for printer connection." +msgstr "프린터 연결을 위한 USB/시리얼 포트." + +#: xs/src/libslic3r/PrintConfig.cpp:1450 +msgid "Serial port speed" +msgstr "시리얼 포트 속도" + +#: xs/src/libslic3r/PrintConfig.cpp:1451 +msgid "Speed (baud) of USB/serial port for printer connection." +msgstr "프린터 연결을 위한 USB/시리얼 포트의 속도(보드)" + +#: xs/src/libslic3r/PrintConfig.cpp:1460 +msgid "Distance from object" +msgstr "객체로부터의 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:1461 +msgid "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion." +msgstr "스커트와 객체 사이의 거리. 스커트를 객체에 부착하고 접착력을 높이기 위해 이를 0으로 설정한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1469 +msgid "Skirt height" +msgstr "스커트(Skirt) 높이" + +#: xs/src/libslic3r/PrintConfig.cpp:1470 +msgid "Height of skirt expressed in layers. Set this to a tall value to use skirt as a shield against drafts." +msgstr "스커트의 높이 레이어로 표현된다. 이를 높은 값으로 설정하여 스커트를 드래프트에 대한 쉴ㄷ로 활용하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1477 +msgid "Loops (minimum)" +msgstr "루프 (최소)" + +#: xs/src/libslic3r/PrintConfig.cpp:1478 +msgid "Skirt Loops" +msgstr "스커트 루프" + +#: xs/src/libslic3r/PrintConfig.cpp:1479 +msgid "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely." +msgstr "스커트의 루프 수입니다. 최소 압출 길이 옵션을 설정한 경우 여기에 구성된 루프 수보다 클 수 있다. 스커트를 완전히 비활성화하려면 이 값을 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1487 +msgid "Slow down if layer print time is below" +msgstr "레이어 인쇄 시간이 다음과 같은 경우 속도를 낮추십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1488 +msgid "If layer print time is estimated below this number of seconds, print moves speed will be scaled down to extend duration to this value." +msgstr "층 인쇄 시간이 이 시간보다 낮게 추정될 경우, 인쇄 이동 속도는 이 값으로 지속되도록 축소된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1498 +msgid "Small perimeters" +msgstr "작은 둘레" + +#: xs/src/libslic3r/PrintConfig.cpp:1500 +msgid "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto." +msgstr "이 개별 설정은 반경이 6.5mm 미만인 속도 (일반적으로 구멍)에 영향을줍니다. 백분율로 표시되는 경우 (예 : 80 %) 위의 속도 설정에서 계산됩니다. 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1510 +msgid "Solid infill threshold area" +msgstr "솔리드 채우기 임계값 영역" + +#: xs/src/libslic3r/PrintConfig.cpp:1512 +msgid "Force solid infill for regions having a smaller area than the specified threshold." +msgstr "지정된 한계값보다 작은 영역을 가진 영역에 대해 솔리드 인필을 강제 적용" + +#: xs/src/libslic3r/PrintConfig.cpp:1513 +msgid "mm²" +msgstr "" + +#: xs/src/libslic3r/PrintConfig.cpp:1519 +msgid "Solid infill extruder" +msgstr "솔리드 인필 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1521 +msgid "The extruder to use when printing solid infill." +msgstr "꽉찬 면을 인쇄할 때 사용하는 익스트루더." + +#: xs/src/libslic3r/PrintConfig.cpp:1527 +msgid "Solid infill every" +msgstr "솔리드 인필 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1529 +msgid "This feature allows to force a solid layer every given number of layers. Zero to disable. You can set this to any value (for example 9999); Slic3r will automatically choose the maximum possible number of layers to combine according to nozzle diameter and layer height." +msgstr "이 특징은 주어진 개수의 층마다 단단한 층을 강요할 수 있게 한다. 비활성화할 수 없음. 당신은 이것을 어떤 값으로도 설정할 수 있다(예: 9999). Slic3r는 노즐 직경과 층 높이에 따라 결합할 최대 가능한 층 수를 자동으로 선택한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1539 xs/src/libslic3r/PrintConfig.cpp:1549 +#: xs/src/libslic3r/GCode/PreviewData.cpp:167 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:94 +msgid "Solid infill" +msgstr "솔리드 인필" + +#: xs/src/libslic3r/PrintConfig.cpp:1541 +msgid "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "이 값을 0이 아닌 값으로 설정하여 솔리드 표면 인필에 대한 수동 압출 폭을 설정하십시오. 0인 경우 기본 압출 너비가 사용되며, 그렇지 않으면 1.125 x 노즐 직경이 사용된다. 백분율(예: 90%)로 표현되는 경우, 계층 높이에 걸쳐 계산된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1551 +msgid "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above. Set to zero for auto." +msgstr "솔리드 영역(상단/하부/내부 수평 셸) 인쇄 속도 이는 위의 기본 주입 속도에 대한 백분율(예: 80%)로 표시할 수 있다. 자동을 위해 0으로 설정한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1563 +msgid "Number of solid layers to generate on top and bottom surfaces." +msgstr "상단 및 하단 표면에 생성할 솔리드 레이어 수입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1570 +msgid "Spiral vase" +msgstr "스파이럴 바이스" + +#: xs/src/libslic3r/PrintConfig.cpp:1571 +msgid "This feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. This option requires a single perimeter, no infill, no top solid layers and no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won't work when printing more than an object." +msgstr "이 기능은 단일 벽 물체를 인쇄하는 동안 눈에 보이는 심을 제거하기 위해 Z를 점진적으로 상승시킨다. 이 옵션은 단일 둘레, 주입, 상단 솔리드 레이어 및 지지 재료가 필요하지 않다. 당신은 스커트/브림 루프뿐만 아니라 아래 솔리드 레이어의 수에 상관없이 설정할 수 있다. 그것은 물체보다 더 많이 인쇄할 때는 작동하지 않을 것이다." + +#: xs/src/libslic3r/PrintConfig.cpp:1580 +msgid "Temperature variation" +msgstr "온도 변화" + +#: xs/src/libslic3r/PrintConfig.cpp:1581 +msgid "Temperature difference to be applied when an extruder is not active. Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped." +msgstr "돌출부가 활성화되지 않은 경우 적용되는 온도 차이. 노즐을 주기적으로 닦는 전체 높이 \"인공\" 스커트가 가능하다." + +#: xs/src/libslic3r/PrintConfig.cpp:1591 +msgid "This start procedure is inserted at the beginning, after bed has reached the target temperature and extruder just started heating, and before extruder has finished heating. If Slic3r detects M104 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want." +msgstr "이 시작 절차는 침대가 목표 온도에 도달하고 압출기가 막 가열을 시작한 직후 및 압출기가 가열을 완료하기 전에 처음에 삽입됩니다. Slic3r이 사용자 지정 코드에서 M104 또는 M190을 감지하면 이러한 명령은 자동으로 추가되지 않으므로 가열 명령 및 기타 사용자 지정 동작의 순서를 자유롭게 사용자 지정할 수 있습니다. 모든 Slic3r 설정에 자리 표시 자 변수를 사용할 수 있으므로 원하는 위치에 \"M109 S [first_layer_temperature]\"명령을 넣을 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1606 +msgid "This start procedure is inserted at the beginning, after any printer start gcode. This is used to override settings for a specific filament. If Slic3r detects M104, M109, M140 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want. If you have multiple extruders, the gcode is processed in extruder order." +msgstr "이 시작 절차는 프린터가 gcode를 시작한 후 처음에 삽입됩니다. 특정 필라멘트의 설정을 무시하는 데 사용됩니다. Slic3r이 사용자 지정 코드에서 M104, M109, M140 또는 M190을 감지하면 이러한 명령은 자동으로 추가되지 않으므로 가열 명령 및 기타 사용자 지정 동작의 순서를 자유롭게 사용자 지정할 수 있습니다. 모든 Slic3r 설정에 자리 표시 자 변수를 사용할 수 있으므로 원하는 위치에 \"M109 S [first_layer_temperature]\"명령을 넣을 수 있습니다. 여러 개의 압출기가있는 경우 gcode가 압출기 순서로 처리됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1621 +msgid "Single Extruder Multi Material" +msgstr "싱글 익스트루더 멀티메터리얼" + +#: xs/src/libslic3r/PrintConfig.cpp:1622 +msgid "The printer multiplexes filaments into a single hot end." +msgstr "프린터는 필라멘트를 하나의 핫 엔드에 멀티플렉싱합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1627 +msgid "Generate support material" +msgstr "서포트 재료 생성" + +#: xs/src/libslic3r/PrintConfig.cpp:1629 +msgid "Enable support material generation." +msgstr "서포트 재료를 사용합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1634 +msgid "XY separation between an object and its support" +msgstr "물체와 그 서포트 사이 XY 분리" + +#: xs/src/libslic3r/PrintConfig.cpp:1636 +msgid "XY separation between an object and its support. If expressed as percentage (for example 50%), it will be calculated over external perimeter width." +msgstr "객체와 그 서포트 사이의 XY 분리. 백분율 (예 : 50 %)로 표시되는 경우 외부 둘레 너비를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1646 +msgid "Pattern angle" +msgstr "채움 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1648 +msgid "Use this setting to rotate the support material pattern on the horizontal plane." +msgstr "이 설정을 사용하여지지 평면 패턴을 수평면으로 회전시킵니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1658 +msgid "Only create support if it lies on a build plate. Don't create support on a print." +msgstr "그것이 빌드 플레이트에있는 경우에만 지원을 작성하십시오. 인쇄물에 대한 지원을 작성하지 마십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1664 +msgid "Contact Z distance" +msgstr "Z 거리 문의" + +#: xs/src/libslic3r/PrintConfig.cpp:1666 +msgid "The vertical distance between object and support material interface. Setting this to 0 will also prevent Slic3r from using bridge flow and speed for the first object layer." +msgstr "물체와 서포트 사이의 수직 거리. 이 값을 0으로 설정하면 Slic3r이 첫 번째 객체 레이어에 브리지 흐름과 속도를 사용하지 못하게됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1674 +msgid "soluble" +msgstr "수용성" + +#: xs/src/libslic3r/PrintConfig.cpp:1675 +msgid "detachable" +msgstr "분리 가능" + +#: xs/src/libslic3r/PrintConfig.cpp:1679 +msgid "Enforce support for the first" +msgstr "첫 번째 서포트 더 강화" + +#: xs/src/libslic3r/PrintConfig.cpp:1681 +msgid "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate." +msgstr "일반지지 소재의 활성화 여부와 관계없이 각도 임계 값에 관계없이 하단에서부터 세어 지정된 레이어 수에 대한지지 자료를 생성합니다. 이것은 빌드 플레이트에 매우 얇거나 부족한 풋 프린트를 가진 물체를 더 많이 부착 할 때 유용합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1687 +msgid "Enforce support for the first n layers" +msgstr "첫 번째 n 개의 레이어에 대한 서포트 강화" + +#: xs/src/libslic3r/PrintConfig.cpp:1692 +msgid "Support material/raft/skirt extruder" +msgstr "서포트 재료 / 라프트 / 스커트 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1694 +msgid "The extruder to use when printing support material, raft and skirt (1+, 0 to use the current extruder to minimize tool changes)." +msgstr "서포트 재료, 라프트 및 스커트를 인쇄 할 때 사용하는 압출기 (도구 변경을 최소화하기 위해 현재 압출기를 사용하려면 1+, 0)." + +#: xs/src/libslic3r/PrintConfig.cpp:1703 +msgid "Set this to a non-zero value to set a manual extrusion width for support material. If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "서포트 재료의 수동 압출 폭을 설정하려면이 값을 0이 아닌 값으로 설정하십시오. 0으로 설정하면 설정된 경우 기본 압출 폭이 사용되고 그렇지 않으면 노즐 지름이 사용됩니다. 백분율 (예 : 90 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1711 +msgid "Interface loops" +msgstr "인터페이스 루프" + +#: xs/src/libslic3r/PrintConfig.cpp:1713 +msgid "Cover the top contact layer of the supports with loops. Disabled by default." +msgstr "지지대의 상단 접촉 층을 루프로 덮으십시오. 기본적으로 사용 안 함." + +#: xs/src/libslic3r/PrintConfig.cpp:1718 +msgid "Support material/raft interface extruder" +msgstr "서포트 재료/라프트 인터페이스 익스트루더" + +#: xs/src/libslic3r/PrintConfig.cpp:1720 +msgid "The extruder to use when printing support material interface (1+, 0 to use the current extruder to minimize tool changes). This affects raft too." +msgstr "서포트 재료 인터페이스를 인쇄 할 때 사용할 익스트루더 (도구 변경을 최소화하기 위해 현재 익스트루더를 사용하려면 1+, 0). 이것은 라프트에도 영향을 미칩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1727 +msgid "Interface layers" +msgstr "인터페이스 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:1729 +msgid "Number of interface layers to insert between the object(s) and support material." +msgstr "객체와 서포트 재료 사이에 삽입할 인터페이스 레이어 수입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1736 +msgid "Interface pattern spacing" +msgstr "인터페이스 패턴 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1738 +msgid "Spacing between interface lines. Set zero to get a solid interface." +msgstr "인터페이스 라인 간 간격. 솔리드 인터페이스를 가져오려면 0을 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1745 +#: xs/src/libslic3r/GCode/PreviewData.cpp:173 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:100 +msgid "Support material interface" +msgstr "서포트 재료 인터페이스" + +#: xs/src/libslic3r/PrintConfig.cpp:1747 +msgid "Speed for printing support material interface layers. If expressed as percentage (for example 50%) it will be calculated over support material speed." +msgstr "서포트 재료 인터페이스 레이어 인쇄 속도 백분율(예: 50%)로 표현될 경우 서포트 재료 속도에 따라 계산된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1756 +msgid "Pattern" +msgstr "패턴" + +#: xs/src/libslic3r/PrintConfig.cpp:1758 +msgid "Pattern used to generate support material." +msgstr "서포트 재료를 생성하는 데 사용되는 패턴." + +#: xs/src/libslic3r/PrintConfig.cpp:1765 +msgid "Rectilinear grid" +msgstr "직선 그리드" + +#: xs/src/libslic3r/PrintConfig.cpp:1770 +msgid "Pattern spacing" +msgstr "패턴 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1772 +msgid "Spacing between support material lines." +msgstr "서포트 재료 라인 사이의 간격" + +#: xs/src/libslic3r/PrintConfig.cpp:1781 +msgid "Speed for printing support material." +msgstr "서포트 재료를 인쇄하는 속도." + +#: xs/src/libslic3r/PrintConfig.cpp:1788 +msgid "Synchronize with object layers" +msgstr "객체 레이어와 동기화" + +#: xs/src/libslic3r/PrintConfig.cpp:1790 +msgid "Synchronize support layers with the object print layers. This is useful with multi-material printers, where the extruder switch is expensive." +msgstr "서포트 레이어를 프린트 레이어와 동기화하십시오. 이것은 스위치가 비싼 멀티 메터리얼 프린터에서 유용하다." + +#: xs/src/libslic3r/PrintConfig.cpp:1796 +msgid "Overhang threshold" +msgstr "오버행 한계점" + +#: xs/src/libslic3r/PrintConfig.cpp:1798 +msgid "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended)." +msgstr "서포트 재료는 경사각(90° = 수직)이 지정된 임계점보다 높은 압출에 대해서는 생성되지 않는다. 즉, 이 값은 서포트 재료 없이 인쇄할 수 있는 가장 수평 경사(수평면에서 측정됨)를 나타낸다. 자동 감지를 위해 0으로 설정하십시오(권장)." + +#: xs/src/libslic3r/PrintConfig.cpp:1810 +msgid "With sheath around the support" +msgstr "서포트 주변이나 외부로" + +#: xs/src/libslic3r/PrintConfig.cpp:1812 +msgid "Add a sheath (a single perimeter line) around the base support. This makes the support more reliable, but also more difficult to remove." +msgstr "기본 서포트 주위에 외장 (단일 주변 선)을 추가하십시오. 이것은 페이스 업을보다 신뢰성있게 만들뿐만 아니라 제거하기도 어렵습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1819 +msgid "Extruder temperature for layers after the first one. Set this to zero to disable temperature control commands in the output." +msgstr "첫 번째 것 이후에 레이어에 대한 더 낮은 온도. 이 값을 0으로 설정하면 출력에서 ​​온도 제어 명령을 비활성화 할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1822 +msgid "Temperature" +msgstr "온도 " + +#: xs/src/libslic3r/PrintConfig.cpp:1828 +msgid "Detect thin walls" +msgstr "얇은 벽(walls) 감지" + +#: xs/src/libslic3r/PrintConfig.cpp:1830 +msgid "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace)." +msgstr "싱글 너비 벽 (두 부분이 맞지 않는 부분과 무너지는 부분)을 감지합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1836 +msgid "Threads" +msgstr "스레드(Threads)" + +#: xs/src/libslic3r/PrintConfig.cpp:1837 +msgid "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors." +msgstr "스레드는 장기 실행 태스크를 병렬 처리하는 데 사용됩니다. 최적의 스레드 수는 사용 가능한 코어 / 프로세서 수보다 약간 높습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1849 +msgid "This custom code is inserted right before every extruder change. Note that you can use placeholder variables for all Slic3r settings as well as [previous_extruder] and [next_extruder]." +msgstr "이 사용자 정의 코드는 모든 압출기 변경 직전에 삽입됩니다. [previous_extruder] 및 [next_extruder]뿐 아니라 모든 Slic3r 설정에 대해 자리 표시 자 변수를 사용할 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1859 xs/src/libslic3r/PrintConfig.cpp:1870 +#: xs/src/libslic3r/GCode/PreviewData.cpp:168 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:95 +msgid "Top solid infill" +msgstr "가장 윗부분 채움" + +#: xs/src/libslic3r/PrintConfig.cpp:1861 +msgid "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. If expressed as percentage (for example 90%) it will be computed over layer height." +msgstr "이 값을 0이 아닌 값으로 설정하여 상단 서피스에 대한 infill의 수동 압출 폭을 설정합니다. 얇은 압출 성형물을 사용하여 모든 좁은 지역을 채우고 더 매끄러운 마무리를 할 수 있습니다. 0으로 설정된 경우 기본 압출 폭이 사용되고 그렇지 않으면 노즐 지름이 사용됩니다. 백분율 (예 : 90 %)로 표현하면 레이어 높이를 기준으로 계산됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1872 +msgid "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above. Set to zero for auto." +msgstr "상단 솔리드 레이어 인쇄 속도 (솔리드 레이어가 아닌 최상단 외부 레이어에만 적용) 표면을 더 좋게 마무리하려면 속도를 늦추시기 바랍니다. 이것은 위의 고체 충전 속도에 대한 백분율 (예 : 80 %)로 나타낼 수 있습니다. 자동으로 0으로 설정하십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1884 lib/Slic3r/GUI/MainFrame.pm:327 +msgid "Top" +msgstr "윗부분" + +#: xs/src/libslic3r/PrintConfig.cpp:1886 +msgid "Number of solid layers to generate on top surfaces." +msgstr "상단 표면에 생성 할 솔리드 레이어 수입니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1888 +msgid "Top solid layers" +msgstr "탑 솔리드 레이어" + +#: xs/src/libslic3r/PrintConfig.cpp:1893 lib/Slic3r/GUI/Plater/3DPreview.pm:105 +msgid "Travel" +msgstr "이송" + +#: xs/src/libslic3r/PrintConfig.cpp:1894 +msgid "Speed for travel moves (jumps between distant extrusion points)." +msgstr "이동 속도 (먼 돌출 점 사이의 점프)." + +#: xs/src/libslic3r/PrintConfig.cpp:1902 +msgid "Use firmware retraction" +msgstr "펌웨어 철회" + +#: xs/src/libslic3r/PrintConfig.cpp:1903 +msgid "This experimental setting uses G10 and G11 commands to have the firmware handle the retraction. This is only supported in recent Marlin." +msgstr "이 실험 설정은 G10 및 G11 명령을 사용하여 펌웨어에서 취소를 처리하도록합니다. 이것은 최근의 말린에서만 지원됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1909 +msgid "Use relative E distances" +msgstr "상대적인 E 거리 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:1910 +msgid "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values." +msgstr "펌웨어에 상대 E 값이 필요한 경우이 값을 선택하고, 그렇지 않으면 선택하지 마십시오. 대부분의 회사는 절대 값을 사용합니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1916 +msgid "Use volumetric E" +msgstr "용적 E 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:1917 +msgid "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. If your firmware doesn't already know filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' in your start G-code in order to turn volumetric mode on and use the filament diameter associated to the filament selected in Slic3r. This is only supported in recent Marlin." +msgstr "이 실험 설정은 선형 밀리미터 대신에 입방 밀리미터 단위의 E 값을 출력으로 사용합니다. 펌웨어가 필라멘트 직경을 모르는 경우 볼륨 모드를 켜고 선택한 필라멘트와 연결된 필라멘트 직경을 사용하기 위해 시작 G 코드에 'M200 D [filament_diameter_0] T0'과 같은 명령을 입력 할 수 있습니다 Slic3r. 이것은 최근의 말린에서만 지원됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1927 +msgid "Enable variable layer height feature" +msgstr "가변 레이어 높이 기능 사용" + +#: xs/src/libslic3r/PrintConfig.cpp:1928 +msgid "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default." +msgstr "일부 프린터 또는 프린터 설정은 가변 레이어 높이로 인쇄하는 데 어려움이있을 수 있습니다. 기본적으로 사용됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:1934 +msgid "Wipe while retracting" +msgstr "수축시 닦아내십시오." + +#: xs/src/libslic3r/PrintConfig.cpp:1935 +msgid "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders." +msgstr "이 플래그는 누출된 리트랙싱의 블럽 가능성을 최소화하기 위해 수축하는 동안 노즐을 이동시킨다." + +#: xs/src/libslic3r/PrintConfig.cpp:1942 +msgid "Multi material printers may need to prime or purge extruders on tool changes. Extrude the excess material into the wipe tower." +msgstr "멀티 메터리알 프린터는 공구 교환 시 익스트루더를 프라이밍하거나 제거해야 할 수 있다. 과도한 물질을 와이퍼 타워에 돌출시킨다." + +#: xs/src/libslic3r/PrintConfig.cpp:1948 +msgid "Purging volumes - load/unload volumes" +msgstr "볼륨 삭제 - 볼륨 로드/언로드" + +#: xs/src/libslic3r/PrintConfig.cpp:1949 +msgid "This vector saves required volumes to change from/to each tool used on the wipe tower. These values are used to simplify creation of the full purging volumes below. " +msgstr "이 벡터는 와이퍼 작동 타워에 사용되는 각 공구와 교환하는 데 필요한 볼륨을 저장한다. 이러한 값은 아래 전체 삭제 볼륨 생성을 단순화하기 위해 사용된다." + +#: xs/src/libslic3r/PrintConfig.cpp:1956 +msgid "Purging volumes - matrix" +msgstr "볼륨 삭제 - 행렬" + +#: xs/src/libslic3r/PrintConfig.cpp:1957 +msgid "This matrix describes volumes (in cubic milimetres) required to purge the new filament on the wipe tower for any given pair of tools. " +msgstr "이 매트릭스는 주어진 공구 쌍에 대해 새 필라멘트를 지우는 데 필요한 볼륨(입방 밀리미터)을 설명한다." + +#: xs/src/libslic3r/PrintConfig.cpp:1967 +msgid "Position X" +msgstr "X축 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1968 +msgid "X coordinate of the left front corner of a wipe tower" +msgstr "와이프 타워의 좌측 전면 모서리의 X 좌표" + +#: xs/src/libslic3r/PrintConfig.cpp:1974 +msgid "Position Y" +msgstr "Y축 위치" + +#: xs/src/libslic3r/PrintConfig.cpp:1975 +msgid "Y coordinate of the left front corner of a wipe tower" +msgstr "와이퍼 작동 타워의 좌측 전방 모서리의 Y 좌표" + +#: xs/src/libslic3r/PrintConfig.cpp:1981 lib/Slic3r/GUI/Plater/3DPreview.pm:76 +msgid "Width" +msgstr "폭" + +#: xs/src/libslic3r/PrintConfig.cpp:1982 +msgid "Width of a wipe tower" +msgstr "와이퍼 타워 폭" + +#: xs/src/libslic3r/PrintConfig.cpp:1988 +msgid "Wipe tower rotation angle" +msgstr "와이퍼 타워 회전각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1989 +msgid "Wipe tower rotation angle with respect to x-axis " +msgstr "X 축에 대한 와이퍼 타워 각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1990 +msgid "degrees" +msgstr "각도" + +#: xs/src/libslic3r/PrintConfig.cpp:1996 +msgid "Purging into infill" +msgstr "인필로 제거" + +#: xs/src/libslic3r/PrintConfig.cpp:1997 +msgid "Wiping after toolchange will be preferentially done inside infills. This lowers the amount of waste but may result in longer print time due to additional travel moves." +msgstr "공구 교체 후 닦는 작업은 우선적으로 인필 내부에서 수행됩니다. 이렇게하면 낭비되는 양은 줄어들지만 추가 이동으로 인해 인쇄 시간이 길어질 수 있습니다." + +#: xs/src/libslic3r/PrintConfig.cpp:2005 +msgid "Purging into objects" +msgstr "객체로 제거" + +#: xs/src/libslic3r/PrintConfig.cpp:2006 +msgid "Objects will be used to wipe the nozzle after a toolchange to save material that would otherwise end up in the wipe tower and decrease print time. Colours of the objects will be mixed as a result." +msgstr "도구 교환 후 노즐을 닦아내면 닦아내는 재료가 절약되어 인쇄 시간이 단축됩니다. 결과적으로 오브젝트의 색상이 혼합됩니다." + +#: xs/src/libslic3r/PrintConfig.cpp:2013 +msgid "Maximal bridging distance" +msgstr "최대 브리징 거리" + +#: xs/src/libslic3r/PrintConfig.cpp:2014 +msgid "Maximal distance between supports on sparse infill sections. " +msgstr "드문드문하 인필 섹션에서 지지대 사이의 최대 거리." + +#: xs/src/libslic3r/PrintConfig.cpp:2020 +msgid "XY Size Compensation" +msgstr "XY 크기 보정" + +#: xs/src/libslic3r/PrintConfig.cpp:2022 +msgid "The object will be grown/shrunk in the XY plane by the configured value (negative = inwards, positive = outwards). This might be useful for fine-tuning hole sizes." +msgstr "XY 평면에서 설정된 값(음수 = 안, 양 = 바깥쪽)에 따라 객체가 증가/정격된다. 이는 구멍 크기를 미세 조정하는데 유용할 수 있다." + +#: xs/src/libslic3r/PrintConfig.cpp:2030 +msgid "Z offset" +msgstr "Z 오프셋" + +#: xs/src/libslic3r/PrintConfig.cpp:2031 +msgid "This value will be added (or subtracted) from all the Z coordinates in the output G-code. It is used to compensate for bad Z endstop position: for example, if your endstop zero actually leaves the nozzle 0.3mm far from the print bed, set this to -0.3 (or fix your endstop)." +msgstr "이 값은 출력 G-코드의 모든 Z 좌표에서 추가(또는 감산)된다. 예를 들어, 엔드 스톱 0이 실제로 노즐을 프린트 베드에서 0.3mm 떨어진 곳에 둔 경우, 이를 -0.3(또는 엔드 스톱을 고정)으로 설정하십시오." + +#: xs/src/libslic3r/GCode/PreviewData.cpp:163 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:90 +msgid "Perimeter" +msgstr "가장자리" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:164 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:91 +msgid "External perimeter" +msgstr "외부 가장자리" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:165 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:92 +msgid "Overhang perimeter" +msgstr "오버행(Overhang) 둘레" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:166 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:93 +msgid "Internal infill" +msgstr "내부 채움" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:169 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:96 +msgid "Bridge infill" +msgstr "프릿지 채움" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:176 +msgid "Mixed" +msgstr "뒤석음" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:367 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:74 +msgid "Feature type" +msgstr "특색 유형" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:369 +msgid "Height (mm)" +msgstr "높이 (mm)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:371 +msgid "Width (mm)" +msgstr "폭 (mm)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:373 +msgid "Speed (mm/s)" +msgstr "속도 (mm/s)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:375 +msgid "Volumetric flow rate (mm3/s)" +msgstr "용적 유량값 (mm3/s)" + +#: xs/src/libslic3r/GCode/PreviewData.cpp:377 +#: lib/Slic3r/GUI/Plater/3DPreview.pm:79 +msgid "Tool" +msgstr "도구" + +#: lib/Slic3r/GUI.pm:308 +msgid "Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "파일을 선택하세요 (STL/OBJ/AMF/3MF/PRUSA):" + +#: lib/Slic3r/GUI/MainFrame.pm:66 +msgid "Version " +msgstr "버전" + +#: lib/Slic3r/GUI/MainFrame.pm:66 +msgid " - Remember to check for updates at http://github.com/prusa3d/slic3r/releases" +msgstr " -http://github.com/prusa3d/slic3r/releases에서 업데이트를 확인하는 것을 잊지 마십시오" + +#: lib/Slic3r/GUI/MainFrame.pm:135 +msgid "Plater" +msgstr "플레이트" + +#: lib/Slic3r/GUI/MainFrame.pm:137 +msgid "Controller" +msgstr "컨트롤러" + +#: lib/Slic3r/GUI/MainFrame.pm:215 +msgid "Open STL/OBJ/AMF/3MF…\tCtrl+O" +msgstr "오픈 STL/OBJ/AMF/3MF…\tCtrl+O" + +#: lib/Slic3r/GUI/MainFrame.pm:215 +msgid "Open a model" +msgstr "오픈 모델" + +#: lib/Slic3r/GUI/MainFrame.pm:218 +msgid "&Load Config…\tCtrl+L" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:218 +msgid "Load exported configuration file" +msgstr "내 보낸 구성 파일로드" + +#: lib/Slic3r/GUI/MainFrame.pm:221 +msgid "&Export Config…\tCtrl+E" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:221 +msgid "Export current configuration to file" +msgstr "현재 구성을 파일로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:224 +msgid "&Load Config Bundle…" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:224 +msgid "Load presets from a bundle" +msgstr "번들에서 미리 설정로드" + +#: lib/Slic3r/GUI/MainFrame.pm:227 +msgid "&Export Config Bundle…" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:227 +msgid "Export all presets to file" +msgstr "모든 이전 설정을 파일로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:232 +msgid "Q&uick Slice…\tCtrl+U" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:232 +msgid "Slice a file into a G-code" +msgstr "파일을 G 코드로 분할" + +#: lib/Slic3r/GUI/MainFrame.pm:238 +msgid "Quick Slice and Save &As…\tCtrl+Alt+U" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:238 +msgid "Slice a file into a G-code, save as" +msgstr "파일을 G 코드로 분할하고 다음으로 저장" + +#: lib/Slic3r/GUI/MainFrame.pm:244 +msgid "&Repeat Last Quick Slice\tCtrl+Shift+U" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:244 +msgid "Repeat last quick slice" +msgstr "마지막으로 빠른 슬라이스 반복" + +#: lib/Slic3r/GUI/MainFrame.pm:251 +msgid "Slice to SV&G…\tCtrl+G" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:251 +msgid "Slice file to a multi-layer SVG" +msgstr "파일을 다중 레이어 SVG로 슬라이스" + +#: lib/Slic3r/GUI/MainFrame.pm:255 +msgid "(&Re)Slice Now\tCtrl+S" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:255 +msgid "Start new slicing process" +msgstr "새로운 슬라이싱 작업 시작" + +#: lib/Slic3r/GUI/MainFrame.pm:258 +msgid "Repair STL file…" +msgstr "STL 파일 복구 ..." + +#: lib/Slic3r/GUI/MainFrame.pm:258 +msgid "Automatically repair an STL file" +msgstr "STL 파일을 자동으로 복구합니다." + +#: lib/Slic3r/GUI/MainFrame.pm:262 +msgid "&Quit" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:262 +msgid "Quit Slic3r" +msgstr "Slic3r 종료" + +#: lib/Slic3r/GUI/MainFrame.pm:272 +msgid "Export G-code..." +msgstr "G-코드 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:272 +msgid "Export current plate as G-code" +msgstr "현재 플레이트를 G 코드로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:275 +msgid "Export plate as STL..." +msgstr "STL로 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:275 +msgid "Export current plate as STL" +msgstr "현재 플레이트를 STL로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:278 +msgid "Export plate as AMF..." +msgstr "AMF로 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:278 +msgid "Export current plate as AMF" +msgstr "현재 플레이트를AMF로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:281 +msgid "Export plate as 3MF..." +msgstr "3MF로 내보내기..." + +#: lib/Slic3r/GUI/MainFrame.pm:281 +msgid "Export current plate as 3MF" +msgstr "현재 플레이트를 3MF로 내보내기" + +#: lib/Slic3r/GUI/MainFrame.pm:294 +msgid "Select &Plater Tab\tCtrl+1" +msgstr "선택 및 플래이트 탭 Ctrl + 1" + +#: lib/Slic3r/GUI/MainFrame.pm:294 +msgid "Show the plater" +msgstr "플레이트를 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:300 +msgid "Select &Controller Tab\tCtrl+T" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:300 +msgid "Show the printer controller" +msgstr "프린터 컨트롤러 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:308 +msgid "Select P&rint Settings Tab\tCtrl+2" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:308 +msgid "Show the print settings" +msgstr "인쇄 설정 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:311 +msgid "Select &Filament Settings Tab\tCtrl+3" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:311 +msgid "Show the filament settings" +msgstr "필라멘트 설정보기" + +#: lib/Slic3r/GUI/MainFrame.pm:314 +msgid "Select Print&er Settings Tab\tCtrl+4" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:314 +msgid "Show the printer settings" +msgstr "간단한 설정보기" + +#: lib/Slic3r/GUI/MainFrame.pm:326 +msgid "Iso" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:326 +msgid "Iso View" +msgstr "Iso 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:327 +msgid "Top View" +msgstr "위에서 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:328 +msgid "Bottom View" +msgstr "바닥 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:329 +msgid "Front" +msgstr "앞" + +#: lib/Slic3r/GUI/MainFrame.pm:329 +msgid "Front View" +msgstr "앞면 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:330 +msgid "Rear View" +msgstr "뒷면 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:331 +msgid "Left" +msgstr "왼쪽" + +#: lib/Slic3r/GUI/MainFrame.pm:331 +msgid "Left View" +msgstr "왼쪽 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:332 +msgid "Right" +msgstr "오른쪽" + +#: lib/Slic3r/GUI/MainFrame.pm:332 +msgid "Right View" +msgstr "오른쪽 보기" + +#: lib/Slic3r/GUI/MainFrame.pm:338 +msgid "Prusa 3D Drivers" +msgstr "푸르사 3D 드라이버" + +#: lib/Slic3r/GUI/MainFrame.pm:338 +msgid "Open the Prusa3D drivers download page in your browser" +msgstr "브라우저에서 Prusa3D 드라이버 다운로드 페이지를 엽니다." + +#: lib/Slic3r/GUI/MainFrame.pm:341 +msgid "Prusa Edition Releases" +msgstr "Prusa 에디션 릴리스" + +#: lib/Slic3r/GUI/MainFrame.pm:341 +msgid "Open the Prusa Edition releases page in your browser" +msgstr "브라우저에서 Prusa Edition 릴리즈 페이지를 엽니 다." + +#: lib/Slic3r/GUI/MainFrame.pm:348 +msgid "Slic3r &Website" +msgstr "Slic3r 및 웹 사이트" + +#: lib/Slic3r/GUI/MainFrame.pm:348 +msgid "Open the Slic3r website in your browser" +msgstr "브라우저에서 Slic3r 웹 사이트 열기" + +#: lib/Slic3r/GUI/MainFrame.pm:351 +msgid "Slic3r &Manual" +msgstr "Slic3r &메뉴얼" + +#: lib/Slic3r/GUI/MainFrame.pm:351 +msgid "Open the Slic3r manual in your browser" +msgstr "브라우저에서 Slic3r 설명서를 엽니다." + +#: lib/Slic3r/GUI/MainFrame.pm:355 +msgid "System Info" +msgstr "시스템 정보" + +#: lib/Slic3r/GUI/MainFrame.pm:355 +msgid "Show system information" +msgstr "시스템 정보 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:358 +msgid "Show &Configuration Folder" +msgstr "폴더 표시 및 구성" + +#: lib/Slic3r/GUI/MainFrame.pm:358 +msgid "Show user configuration folder (datadir)" +msgstr "사용자 구성 폴더 표시 (datadir)" + +#: lib/Slic3r/GUI/MainFrame.pm:361 +msgid "Report an Issue" +msgstr "문제보고" + +#: lib/Slic3r/GUI/MainFrame.pm:361 +msgid "Report an issue on the Slic3r Prusa Edition" +msgstr "Slic3r Prusa Edition에 관한 문제점 보고" + +#: lib/Slic3r/GUI/MainFrame.pm:364 +msgid "&About Slic3r" +msgstr "&Slic3r에 대하여" + +#: lib/Slic3r/GUI/MainFrame.pm:364 +msgid "Show about dialog" +msgstr "대화상자 표시" + +#: lib/Slic3r/GUI/MainFrame.pm:374 +msgid "&File" +msgstr "&파일" + +#: lib/Slic3r/GUI/MainFrame.pm:375 +msgid "&Plater" +msgstr "&플레이트" + +#: lib/Slic3r/GUI/MainFrame.pm:376 +msgid "&Object" +msgstr "&객체" + +#: lib/Slic3r/GUI/MainFrame.pm:377 +msgid "&Window" +msgstr "&윈도우" + +#: lib/Slic3r/GUI/MainFrame.pm:378 +msgid "&View" +msgstr "&보다" + +#: lib/Slic3r/GUI/MainFrame.pm:381 +msgid "&Help" +msgstr "&도움말" + +#: lib/Slic3r/GUI/MainFrame.pm:412 +msgid "Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):" +msgstr "슬라이스 할 파일을 선택하십시오 (STL / OBJ / AMF / 3MF / PRUSA):" + +#: lib/Slic3r/GUI/MainFrame.pm:424 +msgid "No previously sliced file." +msgstr "이전에 분리 된 파일이 없습니다." + +#: lib/Slic3r/GUI/MainFrame.pm:425 lib/Slic3r/GUI/Plater.pm:1405 +msgid "Error" +msgstr "에러" + +#: lib/Slic3r/GUI/MainFrame.pm:429 +msgid "Previously sliced file (" +msgstr "이전에 분리 된 파일 (" + +#: lib/Slic3r/GUI/MainFrame.pm:429 +msgid ") not found." +msgstr ")을 찾을 수 없습니다." + +#: lib/Slic3r/GUI/MainFrame.pm:430 +msgid "File Not Found" +msgstr "파일을 찾을수 없다" + +#: lib/Slic3r/GUI/MainFrame.pm:469 +msgid "SVG" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:469 +msgid "G-code" +msgstr "" + +#: lib/Slic3r/GUI/MainFrame.pm:469 lib/Slic3r/GUI/Plater.pm:1795 +msgid " file as:" +msgstr " 다음 파일 :" + +#: lib/Slic3r/GUI/MainFrame.pm:483 +msgid "Slicing…" +msgstr "슬라이싱..." + +#: lib/Slic3r/GUI/MainFrame.pm:483 +msgid "Processing " +msgstr "프로세싱" + +#: lib/Slic3r/GUI/MainFrame.pm:503 +msgid " was successfully sliced." +msgstr "성공적으로 슬라이스." + +#: lib/Slic3r/GUI/MainFrame.pm:505 +msgid "Slicing Done!" +msgstr "슬라이스 완료!" + +#: lib/Slic3r/GUI/MainFrame.pm:521 +msgid "Select the STL file to repair:" +msgstr "복구 할 STL 파일을 선택하십시오." + +#: lib/Slic3r/GUI/MainFrame.pm:535 +msgid "Save OBJ file (less prone to coordinate errors than STL) as:" +msgstr "OBJ 파일을 저장하십시오 (STL보다 오류를 덜 조정할 가능성이 적음)." + +#: lib/Slic3r/GUI/MainFrame.pm:549 +msgid "Your file was repaired." +msgstr "파일이 복구되었습니다." + +#: lib/Slic3r/GUI/MainFrame.pm:549 +msgid "Repair" +msgstr "수정" + +#: lib/Slic3r/GUI/MainFrame.pm:560 +msgid "Save configuration as:" +msgstr "구성을 저장 :" + +#: lib/Slic3r/GUI/MainFrame.pm:578 lib/Slic3r/GUI/MainFrame.pm:622 +msgid "Select configuration to load:" +msgstr "로드 할 구성 선택 :" + +#: lib/Slic3r/GUI/MainFrame.pm:601 +msgid "Save presets bundle as:" +msgstr "이전 설정 번들을 다음과 같이 저장 :" + +#: lib/Slic3r/GUI/MainFrame.pm:642 +#, perl-format +msgid "%d presets successfully imported." +msgstr "% d 사전 설정을 가져 왔습니다." + +#: lib/Slic3r/GUI/Plater.pm:164 lib/Slic3r/GUI/Plater.pm:2323 +msgid "3D" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:206 +msgid "2D" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:224 +msgid "Layers" +msgstr "레이어" + +#: lib/Slic3r/GUI/Plater.pm:250 lib/Slic3r/GUI/Plater.pm:268 +msgid "Add…" +msgstr "추가..." + +#: lib/Slic3r/GUI/Plater.pm:252 lib/Slic3r/GUI/Plater.pm:270 +msgid "Delete All" +msgstr "전부 지움" + +#: lib/Slic3r/GUI/Plater.pm:253 lib/Slic3r/GUI/Plater.pm:271 +msgid "Arrange" +msgstr "정렬" + +#: lib/Slic3r/GUI/Plater.pm:255 +msgid "More" +msgstr "조금 더" + +#: lib/Slic3r/GUI/Plater.pm:256 +msgid "Fewer" +msgstr "적게" + +#: lib/Slic3r/GUI/Plater.pm:258 +msgid "45° ccw" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:259 +msgid "45° cw" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:260 lib/Slic3r/GUI/Plater.pm:276 +msgid "Scale…" +msgstr "크기." + +#: lib/Slic3r/GUI/Plater.pm:261 lib/Slic3r/GUI/Plater.pm:277 +#: lib/Slic3r/GUI/Plater.pm:2293 +msgid "Split" +msgstr "쪼개기" + +#: lib/Slic3r/GUI/Plater.pm:262 lib/Slic3r/GUI/Plater.pm:278 +#: lib/Slic3r/GUI/Plater.pm:2296 +msgid "Cut…" +msgstr "자르기." + +#: lib/Slic3r/GUI/Plater.pm:264 lib/Slic3r/GUI/Plater.pm:279 +#: lib/Slic3r/GUI/Plater.pm:2300 +msgid "Settings…" +msgstr "설정." + +#: lib/Slic3r/GUI/Plater.pm:265 +msgid "Layer Editing" +msgstr "레이어 편집" + +#: lib/Slic3r/GUI/Plater.pm:280 +msgid "Layer editing" +msgstr "레이어 편집" + +#: lib/Slic3r/GUI/Plater.pm:303 +msgid "Name" +msgstr "이름" + +#: lib/Slic3r/GUI/Plater.pm:304 lib/Slic3r/GUI/Plater.pm:992 +msgid "Copies" +msgstr "사본" + +#: lib/Slic3r/GUI/Plater.pm:305 lib/Slic3r/GUI/Plater.pm:1158 +#: lib/Slic3r/GUI/Plater.pm:1163 lib/Slic3r/GUI/Plater.pm:2262 +msgid "Scale" +msgstr "크기" + +#: lib/Slic3r/GUI/Plater.pm:322 +msgid "Export G-code…" +msgstr "G-코드 내보내기..." + +#: lib/Slic3r/GUI/Plater.pm:323 +msgid "Slice now" +msgstr "지금 자르기" + +#: lib/Slic3r/GUI/Plater.pm:324 +msgid "Print…" +msgstr "프린트..." + +#: lib/Slic3r/GUI/Plater.pm:325 +msgid "Send to printer" +msgstr "프린터로 보내기" + +#: lib/Slic3r/GUI/Plater.pm:326 +msgid "Export STL…" +msgstr "STL로 내보내기..." + +#: lib/Slic3r/GUI/Plater.pm:453 +msgid "Print settings" +msgstr "프린트 설정" + +#: lib/Slic3r/GUI/Plater.pm:455 +msgid "Printer" +msgstr "프린터" + +#: lib/Slic3r/GUI/Plater.pm:488 +msgid "Info" +msgstr "정보" + +#: lib/Slic3r/GUI/Plater.pm:499 +msgid "Volume" +msgstr "크기" + +#: lib/Slic3r/GUI/Plater.pm:500 +msgid "Facets" +msgstr "측면" + +#: lib/Slic3r/GUI/Plater.pm:501 +msgid "Materials" +msgstr "재료" + +#: lib/Slic3r/GUI/Plater.pm:502 +msgid "Manifold" +msgstr "많은" + +#: lib/Slic3r/GUI/Plater.pm:527 +msgid "Sliced Info" +msgstr "슬라이스된 정보" + +#: lib/Slic3r/GUI/Plater.pm:713 +msgid "Loading…" +msgstr "로딩…" + +#: lib/Slic3r/GUI/Plater.pm:713 lib/Slic3r/GUI/Plater.pm:727 +msgid "Processing input file\n" +msgstr "입력 파일 처리\n" + +#: lib/Slic3r/GUI/Plater.pm:750 +msgid "" +"This file contains several objects positioned at multiple heights. Instead of considering them as multiple objects, should I consider\n" +"this file as a single object having multiple parts?\n" +msgstr "" +"이 파일에는 여러 높이에 위치한 여러 객체가 들어 있습니다. 여러 객체로 간주하는 대신,\n" +"이 파일은 여러 부분을 갖는 단일 객체로 보입니까?\n" + +#: lib/Slic3r/GUI/Plater.pm:753 lib/Slic3r/GUI/Plater.pm:770 +msgid "Multi-part object detected" +msgstr "다중 부품 객체가 감지" + +#: lib/Slic3r/GUI/Plater.pm:767 +msgid "" +"Multiple objects were loaded for a multi-material printer.\n" +"Instead of considering them as multiple objects, should I consider\n" +"these files to represent a single object having multiple parts?\n" +msgstr "" +"다중 재료 프린터에 대해 여러 객체가로드되었습니다.\n" +"여러 객체로 간주하는 대신,\n" +"이 파일들은 여러 부분을 갖는 단일 객체를 나타낼 수 있습니까?\n" + +#: lib/Slic3r/GUI/Plater.pm:779 +msgid "Loaded " +msgstr "로드(loaded)" + +#: lib/Slic3r/GUI/Plater.pm:837 +msgid "Your object appears to be too large, so it was automatically scaled down to fit your print bed." +msgstr "개체가 너무 커서 인쇄물에 맞게 자동으로 축소되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:838 +msgid "Object too large?" +msgstr "개체가 너무 큽니까?" + +#: lib/Slic3r/GUI/Plater.pm:992 +msgid "Enter the number of copies of the selected object:" +msgstr "선택한 객체의 사본 수를 입력하십시오 :" + +#: lib/Slic3r/GUI/Plater.pm:1019 +msgid "" +"\n" +"Non-positive value." +msgstr "" +"\n" +"양수가 아닌 값." + +#: lib/Slic3r/GUI/Plater.pm:1020 +msgid "" +"\n" +"Not a numeric value." +msgstr "" +"\n" +"숫자 값이 아닙니다." + +#: lib/Slic3r/GUI/Plater.pm:1021 +msgid "Slic3r Error" +msgstr "Slic3r 에러" + +#: lib/Slic3r/GUI/Plater.pm:1042 +msgid "Enter the rotation angle:" +msgstr "회전 각도를 입력하십시오 :" + +#: lib/Slic3r/GUI/Plater.pm:1042 +msgid "Rotate around " +msgstr "회전" + +#: lib/Slic3r/GUI/Plater.pm:1042 +msgid "Invalid rotation angle entered" +msgstr "잘못된 회전 각도가 입력되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1132 +#, perl-format +msgid "Enter the new size for the selected object (print bed: %smm):" +msgstr "선택한 객체의 새 크기를 입력하십시오 (인쇄 침대 : % smm)." + +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 +msgid "Scale along " +msgstr "전체 크기" + +#: lib/Slic3r/GUI/Plater.pm:1133 lib/Slic3r/GUI/Plater.pm:1137 +#: lib/Slic3r/GUI/Plater.pm:1158 lib/Slic3r/GUI/Plater.pm:1163 +msgid "Invalid scaling value entered" +msgstr "잘못된 배율 값이 입력되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1137 lib/Slic3r/GUI/Plater.pm:1163 +#, no-perl-format +msgid "Enter the scale % for the selected object:" +msgstr "선택한 객체의 비율 %를 입력하십시오." + +#: lib/Slic3r/GUI/Plater.pm:1158 +msgid "Enter the new max size for the selected object:" +msgstr "선택한 객체의 새로운 최대 크기를 입력하십시오." + +#: lib/Slic3r/GUI/Plater.pm:1218 +msgid "The selected object can't be split because it contains more than one volume/material." +msgstr "선택한 객체는 둘 이상의 볼륨 / 재료가 포함되어 있기 때문에 분할 할 수 없습니다." + +#: lib/Slic3r/GUI/Plater.pm:1227 +msgid "The selected object couldn't be split because it contains only one part." +msgstr "선택한 오브젝트는 파트가 하나만 포함되어 있기 때문에 분할 할 수 없습니다." + +#: lib/Slic3r/GUI/Plater.pm:1391 +msgid "Slicing cancelled" +msgstr "슬라이싱 취소됨" + +#: lib/Slic3r/GUI/Plater.pm:1405 +msgid "Another export job is currently running." +msgstr "다른 내보내기 작업이 현재 실행 중입니다." + +#: lib/Slic3r/GUI/Plater.pm:1555 +msgid "File added to print queue" +msgstr "파일이 인쇄 대기열에 추가되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1561 +msgid "G-code file exported to " +msgstr "G 코드 파일을 내보냈습니다." + +#: lib/Slic3r/GUI/Plater.pm:1564 +msgid "Export failed" +msgstr "내보내기 실패" + +#: lib/Slic3r/GUI/Plater.pm:1576 +msgid "OctoPrint upload finished." +msgstr "OctoPrint 업로드가 완료되었습니다." + +#: lib/Slic3r/GUI/Plater.pm:1610 +msgid "Used Filament (m)" +msgstr "사용자 필라멘트 (m)" + +#: lib/Slic3r/GUI/Plater.pm:1612 +msgid "Used Filament (mm³)" +msgstr "사용자 필라멘트 (mm³)" + +#: lib/Slic3r/GUI/Plater.pm:1614 +msgid "Used Filament (g)" +msgstr "사용자 필라멘트 (g)" + +#: lib/Slic3r/GUI/Plater.pm:1618 +msgid "Estimated printing time (normal mode)" +msgstr "예상 인쇄 시간 (일반 모드)" + +#: lib/Slic3r/GUI/Plater.pm:1620 +msgid "Estimated printing time (silent mode)" +msgstr "예상 인쇄 시간 (무으 모드)" + +#: lib/Slic3r/GUI/Plater.pm:1659 lib/Slic3r/GUI/Plater.pm:1701 +msgid "STL file exported to " +msgstr "내보낸 STL 파일" + +#: lib/Slic3r/GUI/Plater.pm:1740 +msgid "AMF file exported to " +msgstr "내보낸 AMF 파일" + +#: lib/Slic3r/GUI/Plater.pm:1744 +msgid "Error exporting AMF file " +msgstr "AMF 파일 내보내기 오류" + +#: lib/Slic3r/GUI/Plater.pm:1756 +msgid "3MF file exported to " +msgstr "3MF 파일을 내보냈습니다" + +#: lib/Slic3r/GUI/Plater.pm:1760 +msgid "Error exporting 3MF file " +msgstr "3MF 파일 내보내기 오류" + +#: lib/Slic3r/GUI/Plater.pm:2140 +#, perl-format +msgid "%d (%d shells)" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:2142 +#, perl-format +msgid "Auto-repaired (%d errors)" +msgstr "오류자동수정 (%d errors)" + +#: lib/Slic3r/GUI/Plater.pm:2147 +#, perl-format +msgid "%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges" +msgstr "%d 면 고정, %d 모서리 고정, %d 면 제거, %d 면 추가, %d 면 반전, %d 후방 모서리" + +#: lib/Slic3r/GUI/Plater.pm:2152 +msgid "Yes" +msgstr "" + +#: lib/Slic3r/GUI/Plater.pm:2215 +msgid "Remove the selected object" +msgstr "선택한 객체 제거" + +#: lib/Slic3r/GUI/Plater.pm:2218 +msgid "Increase copies" +msgstr "복사본 늘리기" + +#: lib/Slic3r/GUI/Plater.pm:2218 +msgid "Place one more copy of the selected object" +msgstr "선택한 객체를 하나 더 복사합니다." + +#: lib/Slic3r/GUI/Plater.pm:2221 +msgid "Decrease copies" +msgstr "복사본 감소" + +#: lib/Slic3r/GUI/Plater.pm:2221 +msgid "Remove one copy of the selected object" +msgstr "선택한 객체 복사본 하나 삭제" + +#: lib/Slic3r/GUI/Plater.pm:2224 +msgid "Set number of copies…" +msgstr "복사될 수량 설정 ..." + +#: lib/Slic3r/GUI/Plater.pm:2224 +msgid "Change the number of copies of the selected object" +msgstr "선택한 개체의 복사본 수 변경" + +#: lib/Slic3r/GUI/Plater.pm:2228 +msgid "Rotate 45° clockwise" +msgstr "시계 방향으로 45도 회전" + +#: lib/Slic3r/GUI/Plater.pm:2228 +msgid "Rotate the selected object by 45° clockwise" +msgstr "객체를 시계 방향으로 45 ° 회전합니다" + +#: lib/Slic3r/GUI/Plater.pm:2231 +msgid "Rotate 45° counter-clockwise" +msgstr "반 시계 방향으로 45 ° 회전" + +#: lib/Slic3r/GUI/Plater.pm:2231 +msgid "Rotate the selected object by 45° counter-clockwise" +msgstr "객체를 시계 방향으로 45 ° 회전합니다" + +#: lib/Slic3r/GUI/Plater.pm:2236 +msgid "Rotate" +msgstr "회전" + +#: lib/Slic3r/GUI/Plater.pm:2236 +msgid "Rotate the selected object by an arbitrary angle" +msgstr "선택한 객체를 임의의 각도로 회전" + +#: lib/Slic3r/GUI/Plater.pm:2238 +msgid "Around X axis…" +msgstr "X축 으로..." + +#: lib/Slic3r/GUI/Plater.pm:2238 +msgid "Rotate the selected object by an arbitrary angle around X axis" +msgstr "선택한 객체를 X 축을 중심으로 임의의 각도만큼 회전" + +#: lib/Slic3r/GUI/Plater.pm:2241 +msgid "Around Y axis…" +msgstr "Y축 으로..." + +#: lib/Slic3r/GUI/Plater.pm:2241 +msgid "Rotate the selected object by an arbitrary angle around Y axis" +msgstr "선택한 객체를 Y 축을 중심으로 임의의 각도만큼 회전" + +#: lib/Slic3r/GUI/Plater.pm:2244 +msgid "Around Z axis…" +msgstr "Z축 으로..." + +#: lib/Slic3r/GUI/Plater.pm:2244 +msgid "Rotate the selected object by an arbitrary angle around Z axis" +msgstr "선택한 객체를 Z 축을 중심으로 임의의 각도만큼 회전" + +#: lib/Slic3r/GUI/Plater.pm:2249 +msgid "Mirror" +msgstr "반전(Mirror)" + +#: lib/Slic3r/GUI/Plater.pm:2249 +msgid "Mirror the selected object" +msgstr "반전할 객제를 선택" + +#: lib/Slic3r/GUI/Plater.pm:2251 lib/Slic3r/GUI/Plater.pm:2267 +#: lib/Slic3r/GUI/Plater.pm:2283 +msgid "Along X axis…" +msgstr "X 축을 따라..." + +#: lib/Slic3r/GUI/Plater.pm:2251 +msgid "Mirror the selected object along the X axis" +msgstr "선택한 객체를 X 축을 따라 반전합니다." + +#: lib/Slic3r/GUI/Plater.pm:2254 lib/Slic3r/GUI/Plater.pm:2270 +#: lib/Slic3r/GUI/Plater.pm:2286 +msgid "Along Y axis…" +msgstr "Y 축을 따라 ..." + +#: lib/Slic3r/GUI/Plater.pm:2254 +msgid "Mirror the selected object along the Y axis" +msgstr "선택한 객체를 Y 축을 따라 반전합니다." + +#: lib/Slic3r/GUI/Plater.pm:2257 lib/Slic3r/GUI/Plater.pm:2273 +#: lib/Slic3r/GUI/Plater.pm:2289 +msgid "Along Z axis…" +msgstr "Z 축을 따라 ..." + +#: lib/Slic3r/GUI/Plater.pm:2257 +msgid "Mirror the selected object along the Z axis" +msgstr "선택한 객체를 Z 축을 따라 반전합니다." + +#: lib/Slic3r/GUI/Plater.pm:2262 lib/Slic3r/GUI/Plater.pm:2278 +msgid "Scale the selected object along a single axis" +msgstr "선택한 객체를 단일 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 +msgid "Uniformly…" +msgstr "균등하게..." + +#: lib/Slic3r/GUI/Plater.pm:2264 lib/Slic3r/GUI/Plater.pm:2280 +msgid "Scale the selected object along the XYZ axes" +msgstr "선택한 객체를 XYZ 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2267 lib/Slic3r/GUI/Plater.pm:2283 +msgid "Scale the selected object along the X axis" +msgstr "선택한 객체를 X 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2270 lib/Slic3r/GUI/Plater.pm:2286 +msgid "Scale the selected object along the Y axis" +msgstr "선택한 객체를 Y 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2273 lib/Slic3r/GUI/Plater.pm:2289 +msgid "Scale the selected object along the Z axis" +msgstr "선택한 객체를 Z 축을 따라 축척합니다." + +#: lib/Slic3r/GUI/Plater.pm:2278 +msgid "Scale to size" +msgstr "크기 조정" + +#: lib/Slic3r/GUI/Plater.pm:2293 +msgid "Split the selected object into individual parts" +msgstr "선택한 객체를 개별 부분으로 분할합니다." + +#: lib/Slic3r/GUI/Plater.pm:2296 +msgid "Open the 3D cutting tool" +msgstr "3차원 컷팅 도구 열기" + +#: lib/Slic3r/GUI/Plater.pm:2300 +msgid "Open the object editor dialog" +msgstr "오브젝트 편집 상자 열기" + +#: lib/Slic3r/GUI/Plater.pm:2304 +msgid "Reload from Disk" +msgstr "디스크에서 다시 불러오기" + +#: lib/Slic3r/GUI/Plater.pm:2304 +msgid "Reload the selected file from Disk" +msgstr "디스크에서 다시 불러오기" + +#: lib/Slic3r/GUI/Plater.pm:2307 +msgid "Export object as STL…" +msgstr "STL로 내보내기..." + +#: lib/Slic3r/GUI/Plater.pm:2307 +msgid "Export this single object as STL file" +msgstr "이 객체를 STL 파일로 내 보냅니다." + +#: lib/Slic3r/GUI/Plater.pm:2311 +msgid "Fix STL through Netfabb" +msgstr "Netfabb를 통해 STL 수정" + +#: lib/Slic3r/GUI/Plater.pm:2311 +msgid "Fix the model by sending it to a Netfabb cloud service through Windows 10 API" +msgstr "Windows 10 API를 통해 Netfabb 클라우드 서비스로 보내 모델 수정" + +#: lib/Slic3r/GUI/Plater/2D.pm:131 +msgid "What do you want to print today? ™" +msgstr "오늘 무엇을 인쇄하고 싶습니까? ™" + +#: lib/Slic3r/GUI/Plater/2D.pm:132 +msgid "Drag your objects here" +msgstr "개체를 여기로 드래그하십시오" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:69 +msgid "1 Layer" +msgstr "1레이어" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:71 +msgid "View" +msgstr "View" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:78 +msgid "Volumetric flow rate" +msgstr "용적의 유량값" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:85 +msgid "Show" +msgstr "보다" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:88 lib/Slic3r/GUI/Plater/3DPreview.pm:89 +msgid "Feature types" +msgstr "특색 유형" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:106 +msgid "Retractions" +msgstr "리트랙션" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:107 +msgid "Unretractions" +msgstr "리트랙션 취소" + +#: lib/Slic3r/GUI/Plater/3DPreview.pm:108 +msgid "Shells" +msgstr "쉘" diff --git a/src/libslic3r/Rasterizer/bicubic.h b/src/libslic3r/Rasterizer/bicubic.h new file mode 100644 index 000000000..870d00dbd --- /dev/null +++ b/src/libslic3r/Rasterizer/bicubic.h @@ -0,0 +1,186 @@ +#ifndef BICUBIC_HPP +#define BICUBIC_HPP + +#include +#include +#include + +#include + +namespace Slic3r { + +namespace BicubicInternal { + // Linear kernel, to be able to test cubic methods with hat kernels. + template + struct LinearKernel + { + typedef T FloatType; + + static T a00() { return T(0.); } + static T a01() { return T(0.); } + static T a02() { return T(0.); } + static T a03() { return T(0.); } + static T a10() { return T(1.); } + static T a11() { return T(-1.); } + static T a12() { return T(0.); } + static T a13() { return T(0.); } + static T a20() { return T(0.); } + static T a21() { return T(1.); } + static T a22() { return T(0.); } + static T a23() { return T(0.); } + static T a30() { return T(0.); } + static T a31() { return T(0.); } + static T a32() { return T(0.); } + static T a33() { return T(0.); } + }; + + // Interpolation kernel aka Catmul-Rom aka Keyes kernel. + template + struct CubicCatmulRomKernel + { + typedef T FloatType; + + static T a00() { return 0; } + static T a01() { return (T)-0.5; } + static T a02() { return (T) 1.; } + static T a03() { return (T)-0.5; } + static T a10() { return (T) 1.; } + static T a11() { return 0; } + static T a12() { return (T)-5./2.; } + static T a13() { return (T) 3./2.; } + static T a20() { return 0; } + static T a21() { return (T) 0.5; } + static T a22() { return (T) 2.; } + static T a23() { return (T)-3./2.; } + static T a30() { return 0; } + static T a31() { return 0; } + static T a32() { return (T)-0.5; } + static T a33() { return (T) 0.5; } + }; + + // B-spline kernel + template + struct CubicBSplineKernel + { + typedef T FloatType; + + static T a00() { return (T) 1./6.; } + static T a01() { return (T) -3./6.; } + static T a02() { return (T) 3./6.; } + static T a03() { return (T) -1./6.; } + static T a10() { return (T) 4./6.; } + static T a11() { return 0; } + static T a12() { return (T) -6./6.; } + static T a13() { return (T) 3./6.; } + static T a20() { return (T) 1./6.; } + static T a21() { return (T) 3./6.; } + static T a22() { return (T) 3./6.; } + static T a23() { return (T)- 3./6.; } + static T a30() { return 0; } + static T a31() { return 0; } + static T a32() { return 0; } + static T a33() { return (T) 1./6.; } + }; + + template + inline T clamp(T a, T lower, T upper) + { + return (a < lower) ? lower : + (a > upper) ? upper : a; + } +} + +template +struct CubicKernel +{ + typedef typename KERNEL KernelInternal; + typedef typename KERNEL::FloatType FloatType; + + static FloatType kernel(FloatType x) + { + x = fabs(x); + if (x >= (FloatType)2.) + return 0.0f; + if (x <= (FloatType)1.) { + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3; + } + assert(x > (FloatType)1. && x < (FloatType)2.); + x -= (FloatType)1.; + FloatType x2 = x * x; + FloatType x3 = x2 * x; + return KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3; + } + + static FloatType interpolate(FloatType f0, FloatType f1, FloatType f2, FloatType f3, FloatType x) + { + const FloatType x2 = x*x; + const FloatType x3 = x*x*x; + return f0*(KERNEL::a00() + KERNEL::a01() * x + KERNEL::a02() * x2 + KERNEL::a03() * x3) + + f1*(KERNEL::a10() + KERNEL::a11() * x + KERNEL::a12() * x2 + KERNEL::a13() * x3) + + f2*(KERNEL::a20() + KERNEL::a21() * x + KERNEL::a22() * x2 + KERNEL::a23() * x3) + + f3*(KERNEL::a30() + KERNEL::a31() * x + KERNEL::a32() * x2 + KERNEL::a33() * x3); + } +}; + +// Linear splines +typedef CubicKernel> LinearKernelf; +typedef CubicKernel> LinearKerneld; +// Catmul-Rom splines +typedef CubicKernel> CubicCatmulRomKernelf; +typedef CubicKernel> CubicCatmulRomKerneld; +typedef CubicKernel> CubicInterpolationKernelf; +typedef CubicKernel> CubicInterpolationKerneld; +// Cubic B-splines +typedef CubicKernel> CubicBSplineKernelf; +typedef CubicKernel> CubicBSplineKerneld; + +template +static float cubic_interpolate(const Eigen::ArrayBase &F, const typename KERNEL::FloatType pt, const typename KERNEL::FloatType dx) +{ + typedef typename KERNEL::FloatType T; + const int w = int(F.size()); + const int ix = (int)floor(pt); + const T s = pt - (T)ix; + + if (ix > 1 && ix + 2 < w) { + // Inside the fully interpolated region. + return KERNEL::interpolate(F[ix - 1], F[ix], F[ix + 1], F[ix + 2], s); + } + // Transition region. Extend with a constant function. + auto f = [&F, w](x) { return F[BicubicInternal::clamp(x, 0, w - 1)]; } + return KERNEL::interpolate(f(ix - 1), f(ix), f(ix + 1), f(ix + 2), s); +} + +template +static float bicubic_interpolate(const Eigen::MatrixBase &F, const Eigen::Matrix &pt, const typename KERNEL::FloatType dx) +{ + typedef typename KERNEL::FloatType T; + const int w = F.cols(); + const int h = F.rows(); + const int ix = (int)floor(pt[0]); + const int iy = (int)floor(pt[1]); + const T s = pt[0] - (T)ix; + const T t = pt[1] - (T)iy; + + if (ix > 1 && ix + 2 < w && iy > 1 && iy + 2 < h) { + // Inside the fully interpolated region. + return KERNEL::interpolate( + KERNEL::interpolate(F(ix-1,iy-1),F(ix ,iy-1),F(ix+1,iy-1),F(ix+2,iy-1),s), + KERNEL::interpolate(F(ix-1,iy ),F(ix ,iy ),F(ix+1,iy ),F(ix+2,iy ),s), + KERNEL::interpolate(F(ix-1,iy+1),F(ix ,iy+1),F(ix+1,iy+1),F(ix+2,iy+1),s), + KERNEL::interpolate(F(ix-1,iy+2),F(ix ,iy+2),F(ix+1,iy+2),F(ix+2,iy+2),s),t); + } + // Transition region. Extend with a constant function. + auto f = [&f, w, h](int x, int y) { return F(BicubicInternal::clamp(x,0,w-1),BicubicInternal::clamp(y,0,h-1)); } + return KERNEL::interpolate( + KERNEL::interpolate(f(ix-1,iy-1),f(ix ,iy-1),f(ix+1,iy-1),f(ix+2,iy-1),s), + KERNEL::interpolate(f(ix-1,iy ),f(ix ,iy ),f(ix+1,iy ),f(ix+2,iy ),s), + KERNEL::interpolate(f(ix-1,iy+1),f(ix ,iy+1),f(ix+1,iy+1),f(ix+2,iy+1),s), + KERNEL::interpolate(f(ix-1,iy+2),f(ix ,iy+2),f(ix+1,iy+2),f(ix+2,iy+2),s),t); +} + +} // namespace Slic3r + +#endif /* BICUBIC_HPP */ From b31c62e044f773487ae21dd70578e13513803539 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Dec 2018 15:54:54 +0100 Subject: [PATCH 21/61] Added cancellation points to pad creation step. --- sandboxes/slabasebed/slabasebed.cpp | 10 +++--- src/libslic3r/SLA/SLABasePool.cpp | 21 ++++++++--- src/libslic3r/SLA/SLABasePool.hpp | 16 +++++---- src/libslic3r/SLA/SLABoilerPlate.hpp | 2 +- src/libslic3r/SLA/SLASupportTree.cpp | 46 +++++++++++++++++------- src/libslic3r/SLA/SLASupportTree.hpp | 8 ++--- src/libslic3r/SLAPrint.cpp | 54 +++++++++++++++++----------- 7 files changed, 102 insertions(+), 55 deletions(-) diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index 255ce2cc3..9804ea3c9 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -1,10 +1,10 @@ #include #include -#include -#include "TriangleMesh.hpp" -#include "SLABasePool.hpp" -#include "benchmark.h" +#include +#include +#include +#include const std::string USAGE_STR = { "Usage: slabasebed stlfilename.stl" @@ -28,7 +28,7 @@ int main(const int argc, const char *argv[]) { ExPolygons ground_slice; TriangleMesh basepool; - sla::ground_layer(model, ground_slice, 0.1f); + sla::base_plate(model, ground_slice, 0.1f); bench.start(); sla::create_base_pool(ground_slice, basepool); diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 6ecc63576..6f28caf71 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -10,6 +10,10 @@ namespace Slic3r { namespace sla { +namespace { +ThrowOnCancel throw_on_cancel = [](){}; +} + /// Convert the triangulation output to an intermediate mesh. Contour3D convert(const Polygons& triangles, coord_t z, bool dir) { @@ -60,9 +64,13 @@ Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, }); }; + auto& thr = throw_on_cancel; + std::for_each(tri.begin(), tri.end(), - [&rp, &rpi, &poly, &idx, is_upper, fz, cz](const Polygon& pp) + [&rp, &rpi, thr, &idx, is_upper, fz, cz](const Polygon& pp) { + thr(); // may throw if cancellation was requested + for(auto& p : pp.points) if(is_upper(p)) rp.emplace_back(unscale(x(p), y(p), mm(cz))); @@ -231,6 +239,8 @@ Contour3D round_edges(const ExPolygon& base_plate, if(degrees >= 90) { for(int i = 1; i <= steps; ++i) { + throw_on_cancel(); + ob = base_plate; double r2 = radius_mm * radius_mm; @@ -254,6 +264,7 @@ Contour3D round_edges(const ExPolygon& base_plate, int tos = int(tox / stepx); for(int i = 1; i <= tos; ++i) { + throw_on_cancel(); ob = base_plate; double r2 = radius_mm * radius_mm; @@ -385,7 +396,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50) std::back_inserter(punion), [&punion, &boxindex, cc, max_dist_mm, &idx](const Point& c) { - + throw_on_cancel(); double dx = x(c) - x(cc), dy = y(c) - y(cc); double l = std::sqrt(dx * dx + dy * dy); double nx = dx / l, ny = dy / l; @@ -419,7 +430,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50) } void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, - float layerh) + float layerh, ThrowOnCancel thrfn) { TriangleMesh m = mesh; TriangleMeshSlicer slicer(&m); @@ -431,7 +442,7 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, heights.emplace_back(hi); std::vector out; out.reserve(size_t(std::ceil(h/layerh))); - slicer.slice(heights, &out, [](){}); + slicer.slice(heights, &out, thrfn); size_t count = 0; for(auto& o : out) count += o.size(); ExPolygons tmp; tmp.reserve(count); @@ -444,6 +455,8 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, const PoolConfig& cfg) { + throw_on_cancel = cfg.throw_on_cancel; + double mdist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm) + cfg.max_merge_distance_mm; diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 132a7aeac..07a76265a 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -2,6 +2,7 @@ #define SLABASEPOOL_HPP #include +#include namespace Slic3r { @@ -11,12 +12,14 @@ class TriangleMesh; namespace sla { using ExPolygons = std::vector; +using ThrowOnCancel = std::function; /// Calculate the polygon representing the silhouette from the specified height -void base_plate(const TriangleMesh& mesh, - ExPolygons& output, - float zlevel = 0.1f, - float layerheight = 0.05f); +void base_plate(const TriangleMesh& mesh, // input mesh + ExPolygons& output, // Output will be merged with + float zlevel = 0.1f, // Plate creation level + float layerheight = 0.05f, // The sampling height + ThrowOnCancel thrfn = [](){}); // Will be called frequently struct PoolConfig { double min_wall_thickness_mm = 2; @@ -24,6 +27,8 @@ struct PoolConfig { double max_merge_distance_mm = 50; double edge_radius_mm = 1; + ThrowOnCancel throw_on_cancel = [](){}; + inline PoolConfig() {} inline PoolConfig(double wt, double wh, double md, double er): min_wall_thickness_mm(wt), @@ -35,8 +40,7 @@ struct PoolConfig { /// Calculate the pool for the mesh for SLA printing void create_base_pool(const ExPolygons& base_plate, TriangleMesh& output_mesh, - const PoolConfig& = PoolConfig() - ); + const PoolConfig& = PoolConfig()); /// TODO: Currently the base plate of the pool will have half the height of the /// whole pool. So the carved out space has also half the height. This is not diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index 9a79c8e58..1436be17f 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -58,7 +58,7 @@ struct Contour3D { points.insert(points.end(), ctr.points.begin(), ctr.points.end()); indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end()); - for(auto n = s; n < indices.size(); n++) { + for(size_t n = s; n < indices.size(); n++) { auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3; } } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index f66351bdb..6cab763cd 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -515,8 +515,13 @@ struct Pad { zlevel(ground_level + sla::get_pad_elevation(pcfg)) { ExPolygons basep; + cfg.throw_on_cancel(); + + // The 0.1f is the layer height with which the mesh is sampled and then + // the layers are unified into one vector of polygons. base_plate(object_support_mesh, basep, - float(cfg.min_wall_height_mm)/*,layer_height*/); + float(cfg.min_wall_height_mm), 0.1f, pcfg.throw_on_cancel); + for(auto& bp : baseplate) basep.emplace_back(bp); create_base_pool(basep, tmesh, cfg); @@ -622,12 +627,19 @@ class SLASupportTree::Impl { std::vector m_junctions; std::vector m_bridges; std::vector m_compact_bridges; + Controller m_ctl; + Pad m_pad; mutable TriangleMesh meshcache; mutable bool meshcache_valid; mutable double model_height = 0; // the full height of the model public: double ground_level = 0; + Impl() = default; + inline Impl(const Controller& ctl): m_ctl(ctl) {} + + const Controller& ctl() const { return m_ctl; } + template Head& add_head(Args&&... args) { m_heads.emplace_back(std::forward(args)...); m_heads.back().id = long(m_heads.size() - 1); @@ -710,27 +722,38 @@ public: meshcache = TriangleMesh(); for(auto& head : heads()) { + if(m_ctl.stopcondition()) break; auto&& m = mesh(head.mesh); meshcache.merge(m); } for(auto& stick : pillars()) { + if(m_ctl.stopcondition()) break; meshcache.merge(mesh(stick.mesh)); meshcache.merge(mesh(stick.base)); } for(auto& j : junctions()) { + if(m_ctl.stopcondition()) break; meshcache.merge(mesh(j.mesh)); } for(auto& cb : compact_bridges()) { + if(m_ctl.stopcondition()) break; meshcache.merge(mesh(cb.mesh)); } for(auto& bs : bridges()) { + if(m_ctl.stopcondition()) break; meshcache.merge(mesh(bs.mesh)); } + if(m_ctl.stopcondition()) { + // In case of failure we have to return an empty mesh + meshcache = TriangleMesh(); + return meshcache; + } + // TODO: Is this necessary? meshcache.repair(); @@ -1659,22 +1682,19 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const fullmesh.merge(get_pad()); TriangleMeshSlicer slicer(&fullmesh); SlicedSupports ret; - slicer.slice(heights, &ret, m_ctl.cancelfn); + slicer.slice(heights, &ret, get().ctl().cancelfn); return ret; } const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, - double min_wall_thickness_mm, - double min_wall_height_mm, - double max_merge_distance_mm, - double edge_radius_mm) const + const PoolConfig& pcfg) const { - PoolConfig pcfg; - pcfg.min_wall_thickness_mm = min_wall_thickness_mm; - pcfg.min_wall_height_mm = min_wall_height_mm; - pcfg.max_merge_distance_mm = max_merge_distance_mm; - pcfg.edge_radius_mm = edge_radius_mm; +// PoolConfig pcfg; +// pcfg.min_wall_thickness_mm = min_wall_thickness_mm; +// pcfg.min_wall_height_mm = min_wall_height_mm; +// pcfg.max_merge_distance_mm = max_merge_distance_mm; +// pcfg.edge_radius_mm = edge_radius_mm; return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; } @@ -1687,14 +1707,14 @@ SLASupportTree::SLASupportTree(const PointSet &points, const EigenMesh3D& emesh, const SupportConfig &cfg, const Controller &ctl): - m_impl(new Impl()), m_ctl(ctl) + m_impl(new Impl(ctl)) { m_impl->ground_level = emesh.ground_level - cfg.object_elevation_mm; generate(points, emesh, cfg, ctl); } SLASupportTree::SLASupportTree(const SLASupportTree &c): - m_impl(new Impl(*c.m_impl)), m_ctl(c.m_ctl) {} + m_impl(new Impl(*c.m_impl)) {} SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c) { diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 9213101f3..90162d6b5 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -69,6 +69,8 @@ struct SupportConfig { double object_elevation_mm = 10; }; +struct PoolConfig; + /// A Control structure for the support calculation. Consists of the status /// indicator callback and the stop condition predicate. struct Controller { @@ -119,7 +121,6 @@ public: class SLASupportTree { class Impl; std::unique_ptr m_impl; - Controller m_ctl; Impl& get() { return *m_impl; } const Impl& get() const { return *m_impl; } @@ -158,10 +159,7 @@ public: /// Adding the "pad" (base pool) under the supports const TriangleMesh& add_pad(const SliceLayer& baseplate, - double min_wall_thickness_mm, - double min_wall_height_mm, - double max_merge_distance_mm, - double edge_radius_mm) const; + const PoolConfig& pcfg) const; /// Get the pad geometry const TriangleMesh& get_pad() const; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 3cb4920a5..9eb26fe75 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -445,7 +445,7 @@ void SLAPrint::process() // Slicing the model object. This method is oversimplified and needs to // be compared with the fff slicing algorithm for verification - auto slice_model = [this, ilh, ilhd](SLAPrintObject& po) { + auto slice_model = [this, ilh](SLAPrintObject& po) { double lh = po.m_config.layer_height.getFloat(); TriangleMesh mesh = po.transformed_mesh(); @@ -530,6 +530,11 @@ void SLAPrint::process() po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(pts, emesh, scfg, ctl)); + // Create the unified mesh + auto rc = SlicingStatus::RELOAD_SCENE; + set_status(-1, L("Visualizing supports")); + po.m_supportdata->support_tree_ptr->merged_mesh(); + set_status(-1, L("Visualizing supports"), rc); } catch(sla::SLASupportsStoppedException&) { // no need to rethrow // throw_if_canceled(); @@ -560,18 +565,23 @@ void SLAPrint::process() auto&& trmesh = po.transformed_mesh(); // This call can get pretty time consuming - if(elevation < pad_h) sla::base_plate(trmesh, bp, - float(pad_h), float(lh)); + auto thrfn = [this](){ throw_if_canceled(); }; - po.m_supportdata->support_tree_ptr->add_pad(bp, wt, h, md, er); + if(elevation < pad_h) + sla::base_plate(trmesh, bp, float(pad_h), float(lh), + thrfn); + + pcfg.throw_on_cancel = thrfn; + po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); } - // if the base pool (which means also the support tree) is - // done, do a refresh when indicating progress. Now the - // geometries for the supports and the optional base pad are - // ready. We can grant access for the control thread to read - // the geometries, but first we have to update the caches: - po.support_mesh(); /*po->pad_mesh();*/ +// // if the base pool (which means also the support tree) is +// // done, do a refresh when indicating progress. Now the +// // geometries for the supports and the optional base pad are +// // ready. We can grant access for the control thread to read +// // the geometries, but first we have to update the caches: +// po.support_mesh(); /*po->pad_mesh();*/ + po.throw_if_canceled(); auto rc = SlicingStatus::RELOAD_SCENE; set_status(-1, L("Visualizing supports"), rc); }; @@ -589,9 +599,9 @@ void SLAPrint::process() // We have the layer polygon collection but we need to unite them into // an index where the key is the height level in discrete levels (clipper) - auto index_slices = [this, ilh, ilhd](SLAPrintObject& po) { + auto index_slices = [ilhd](SLAPrintObject& po) { po.m_slice_index.clear(); - auto sih = LevelID(scale_(ilh)); + auto sih = LevelID(scale_(ilhd)); // For all print objects, go through its initial layers and place them // into the layers hash @@ -635,7 +645,7 @@ void SLAPrint::process() // shortcut for empty index into the slice vectors static const auto EMPTY_SLICE = SLAPrintObject::SliceRecord::NONE; - for(int i = 0; i < oslices.size(); ++i) { + for(size_t i = 0; i < oslices.size(); ++i) { LevelID h = levelids[i]; float fh = float(double(h) * SCALING_FACTOR); @@ -652,7 +662,7 @@ void SLAPrint::process() po.m_supportdata->level_ids.clear(); po.m_supportdata->level_ids.reserve(sslices.size()); - for(int i = 0; i < sslices.size(); ++i) { + for(int i = 0; i < int(sslices.size()); ++i) { int a = i == 0 ? 0 : 1; int b = i == 0 ? 0 : i - 1; LevelID h = sminZ + a * sih + b * slh; @@ -662,7 +672,7 @@ void SLAPrint::process() SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh]; assert(sr.support_slices_idx == EMPTY_SLICE); - sr.support_slices_idx = i; + sr.support_slices_idx = SLAPrintObject::SliceRecord::Idx(i); } } }; @@ -670,7 +680,7 @@ void SLAPrint::process() auto& levels = m_printer_input; // Rasterizing the model objects, and their supports - auto rasterize = [this, ilh, ilhd, max_objstatus, &levels]() { + auto rasterize = [this, &levels]() { if(canceled()) return; // clear the rasterizer input @@ -688,14 +698,14 @@ void SLAPrint::process() // now merge this object's support and object slices with the rest // of the print object slices - for(int i = 0; i < oslices.size(); ++i) { + for(size_t i = 0; i < oslices.size(); ++i) { auto& lyrs = levels[gndlvl + po.m_level_ids[i]]; lyrs.emplace_back(oslices[i], po.m_instances); } if(!po.m_supportdata) continue; auto& sslices = po.m_supportdata->support_slices; - for(int i = 0; i < sslices.size(); ++i) { + for(size_t i = 0; i < sslices.size(); ++i) { auto& lyrs = levels[gndlvl + po.m_supportdata->level_ids[i]]; lyrs.emplace_back(sslices[i], po.m_instances); } @@ -713,8 +723,8 @@ void SLAPrint::process() double w = printcfg.display_width.getFloat(); double h = printcfg.display_height.getFloat(); - unsigned pw = printcfg.display_pixels_x.getInt(); - unsigned ph = printcfg.display_pixels_y.getInt(); + auto pw = unsigned(printcfg.display_pixels_x.getInt()); + auto ph = unsigned(printcfg.display_pixels_y.getInt()); double lh = ocfg.layer_height.getFloat(); double exp_t = matcfg.exposure_time.getFloat(); double iexp_t = matcfg.initial_exposure_time.getFloat(); @@ -1098,7 +1108,9 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const const TriangleMesh& SLAPrintObject::support_mesh() const { if(m_config.supports_enable.getBool() && m_supportdata && - m_supportdata->support_tree_ptr) return m_supportdata->support_tree_ptr->merged_mesh(); + m_supportdata->support_tree_ptr) { + return m_supportdata->support_tree_ptr->merged_mesh(); + } return EMPTY_MESH; } From 15eae24650073aa2f5afec5a8741942b99b8db9e Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Dec 2018 16:20:30 +0100 Subject: [PATCH 22/61] Build error fix for msvc --- src/libslic3r/SLAPrint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 9eb26fe75..239c86eca 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -680,7 +680,7 @@ void SLAPrint::process() auto& levels = m_printer_input; // Rasterizing the model objects, and their supports - auto rasterize = [this, &levels]() { + auto rasterize = [this, max_objstatus, &levels]() { if(canceled()) return; // clear the rasterizer input From 52db7b055a12f9f38035d74823b7793d0e9b68a3 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Dec 2018 16:33:43 +0100 Subject: [PATCH 23/61] WIP: Merged commits from stable between 1.41.2-beta and 1.42.2 final. Changes in SupportMaterial.cpp, TriangleMesh.cpp and 01_trianglemesh.t are yet to be merged. WIP: Refactoring of layer height editing. Removed layer_height_ranges from PrintObject, as the Print/PrintObject now hold their copies of Model/ModelObject. --- src/libslic3r/EdgeGrid.cpp | 155 ++++++++++++++++++++++- src/libslic3r/Fill/FillGyroid.cpp | 2 +- src/libslic3r/GCode/WipeTowerPrusaMM.cpp | 9 +- src/libslic3r/Model.cpp | 10 +- src/libslic3r/MultiPoint.hpp | 17 +++ src/libslic3r/Point.hpp | 20 ++- src/libslic3r/Polyline.hpp | 4 +- src/libslic3r/Print.cpp | 27 ++-- src/libslic3r/Print.hpp | 1 - src/libslic3r/PrintObject.cpp | 10 +- src/slic3r/GUI/AppConfig.cpp | 10 +- src/slic3r/GUI/Plater.cpp | 6 +- 12 files changed, 231 insertions(+), 40 deletions(-) diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 2b2893c80..c34aca8f2 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -9,7 +9,9 @@ #endif /* SLIC3R_GUI */ #include "libslic3r.h" +#include "ClipperUtils.hpp" #include "EdgeGrid.hpp" +#include "SVG.hpp" #if 0 // Enable debugging and assert in this file. @@ -756,8 +758,8 @@ void EdgeGrid::Grid::calculate_sdf() float search_radius = float(m_resolution<<1); m_signed_distance_field.assign(nrows * ncols, search_radius); // For each cell: - for (size_t r = 0; r < m_rows; ++ r) { - for (size_t c = 0; c < m_cols; ++ c) { + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { const Cell &cell = m_cells[r * m_cols + c]; // For each segment in the cell: for (size_t i = cell.begin; i != cell.end; ++ i) { @@ -842,6 +844,8 @@ void EdgeGrid::Grid::calculate_sdf() #if 0 static int iRun = 0; ++ iRun; + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler); //#ifdef SLIC3R_GUI { wxImage img(ncols, nrows); @@ -1356,9 +1360,101 @@ Polygons EdgeGrid::Grid::contours_simplified(coord_t offset, bool fill_holes) co return out; } +inline int segments_could_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + Vec2i64 iv = (ip2 - ip1).cast(); + Vec2i64 vij1 = (jp1 - ip1).cast(); + Vec2i64 vij2 = (jp2 - ip1).cast(); + int64_t tij1 = cross2(iv, vij1); + int64_t tij2 = cross2(iv, vij2); + int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum + int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); + return sij1 * sij2; +} + +inline bool segments_intersect( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && + segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; +} + +std::vector> EdgeGrid::Grid::intersecting_edges() const +{ + std::vector> out; + // For each cell: + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + // For each pair of segments in the cell: + for (size_t i = cell.begin; i != cell.end; ++ i) { + const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment and their vector. + const Slic3r::Point &ip1 = ipts[ipt]; + const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; + for (size_t j = i + 1; j != cell.end; ++ j) { + const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; + size_t jpt = m_cell_data[j].second; + // End points of the line segment and their vector. + const Slic3r::Point &jp1 = jpts[jpt]; + const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; + if (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) + // Segments of the same contour share a common vertex. + continue; + if (segments_intersect(ip1, ip2, jp1, jp2)) { + // The two segments intersect. Add them to the output. + int jfirst = (&jpts < &ipts) || (&jpts == &ipts && jpt < ipt); + out.emplace_back(jfirst ? + std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt)) : + std::make_pair(std::make_pair(&ipts, ipt), std::make_pair(&jpts, jpt))); + } + } + } + } + } + Slic3r::sort_remove_duplicates(out); + return out; +} + +bool EdgeGrid::Grid::has_intersecting_edges() const +{ + // For each cell: + for (int r = 0; r < (int)m_rows; ++ r) { + for (int c = 0; c < (int)m_cols; ++ c) { + const Cell &cell = m_cells[r * m_cols + c]; + // For each pair of segments in the cell: + for (size_t i = cell.begin; i != cell.end; ++ i) { + const Slic3r::Points &ipts = *m_contours[m_cell_data[i].first]; + size_t ipt = m_cell_data[i].second; + // End points of the line segment and their vector. + const Slic3r::Point &ip1 = ipts[ipt]; + const Slic3r::Point &ip2 = ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]; + for (size_t j = i + 1; j != cell.end; ++ j) { + const Slic3r::Points &jpts = *m_contours[m_cell_data[j].first]; + size_t jpt = m_cell_data[j].second; + // End points of the line segment and their vector. + const Slic3r::Point &jp1 = jpts[jpt]; + const Slic3r::Point &jp2 = jpts[(jpt + 1 == jpts.size()) ? 0 : jpt + 1]; + if (! (&ipts == &jpts && (&ip1 == &jp2 || &jp1 == &ip2)) && + segments_intersect(ip1, ip2, jp1, jp2)) + return true; + } + } + } + } + return false; +} + #if 0 void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) { + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler); + unsigned int w = (bbox.max(0) - bbox.min(0) + resolution - 1) / resolution; unsigned int h = (bbox.max(1) - bbox.min(1) + resolution - 1) / resolution; wxImage img(w, h); @@ -1450,4 +1546,59 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo } #endif /* SLIC3R_GUI */ +// Find all pairs of intersectiong edges from the set of polygons. +std::vector> intersecting_edges(const Polygons &polygons) +{ + double len = 0; + size_t cnt = 0; + BoundingBox bbox; + for (const Polygon &poly : polygons) { + if (poly.points.size() < 2) + continue; + for (size_t i = 0; i < poly.points.size(); ++ i) { + bbox.merge(poly.points[i]); + size_t j = (i == 0) ? (poly.points.size() - 1) : i - 1; + len += (poly.points[j] - poly.points[i]).cast().norm(); + ++ cnt; + } + } + len /= double(cnt); + bbox.offset(20); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(polygons, len); + return grid.intersecting_edges(); +} + +// Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. +void export_intersections_to_svg(const std::string &filename, const Polygons &polygons) +{ + std::vector> intersections = intersecting_edges(polygons); + BoundingBox bbox = get_extents(polygons); + SVG svg(filename.c_str(), bbox); + svg.draw(union_ex(polygons), "gray", 0.25f); + svg.draw_outline(polygons, "black"); + std::set intersecting_contours; + for (const std::pair &ie : intersections) { + intersecting_contours.insert(ie.first.first); + intersecting_contours.insert(ie.second.first); + } + // Highlight the contours with intersections. + coord_t line_width = coord_t(scale_(0.01)); + for (const Points *ic : intersecting_contours) { + svg.draw_outline(Polygon(*ic), "green"); + svg.draw_outline(Polygon(*ic), "black", line_width); + } + // Paint the intersections. + for (const std::pair &intersecting_edges : intersections) { + auto edge = [](const EdgeGrid::Grid::ContourEdge &e) { + return Line(e.first->at(e.second), + e.first->at((e.second + 1 == e.first->size()) ? 0 : e.second + 1)); + }; + svg.draw(edge(intersecting_edges.first), "red", line_width); + svg.draw(edge(intersecting_edges.second), "red", line_width); + } + svg.Close(); +} + } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index d6bf03ce6..04319bb26 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -133,7 +133,7 @@ void FillGyroid::_fill_surface_single( // no rotation is supported for this infill pattern (yet) BoundingBox bb = expolygon.contour.bounding_box(); // Density adjusted to have a good %of weight. - double density_adjusted = std::max(0., params.density * 2.); + double density_adjusted = std::max(0., params.density * 2.44); // Distance between the gyroid waves in scaled coordinates. coord_t distance = coord_t(scale_(this->spacing) / density_adjusted); diff --git a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index f8f5fddd6..5c24cd71c 100644 --- a/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -545,7 +545,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( m_print_brim = true; // Ask our writer about how much material was consumed: - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); ToolChangeResult result; result.priming = true; @@ -698,7 +699,8 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo m_print_brim = false; // Mark the brim as extruded // Ask our writer about how much material was consumed: - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); ToolChangeResult result; result.priming = false; @@ -868,7 +870,8 @@ void WipeTowerPrusaMM::toolchange_Change( material_type new_material) { // Ask the writer about how much of the old filament we consumed: - m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + if (m_current_tool < m_used_filament_length.size()) + m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); // Speed override for the material. Go slow for flex and soluble materials. int speed_override; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 84c34e232..b02153128 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -521,10 +521,14 @@ void Model::adjust_min_z() unsigned int Model::get_auto_extruder_id(unsigned int max_extruders) { unsigned int id = s_auto_extruder_id; - - if (++s_auto_extruder_id > max_extruders) + if (id > max_extruders) { + // The current counter is invalid, likely due to switching the printer profiles + // to a profile with a lower number of extruders. reset_auto_extruder_id(); - + id = s_auto_extruder_id; + } else if (++ s_auto_extruder_id > max_extruders) { + reset_auto_extruder_id(); + } return id; } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 521b4dc4f..38020d6e8 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -107,6 +107,23 @@ extern BoundingBox get_extents(const MultiPoint &mp); extern BoundingBox get_extents_rotated(const std::vector &points, double angle); extern BoundingBox get_extents_rotated(const MultiPoint &mp, double angle); +inline double length(const Points &pts) { + double total = 0; + if (! pts.empty()) { + auto it = pts.begin(); + for (auto it_prev = it ++; it != pts.end(); ++ it, ++ it_prev) + total += (*it - *it_prev).cast().norm(); + } + return total; +} + +inline double area(const Points &polygon) { + double area = 0.; + for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) + area += double(polygon[i](0) + polygon[j](0)) * double(polygon[i](1) - polygon[j](1)); + return area; +} + } // namespace Slic3r #endif diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 277fc5877..82df93cc1 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -187,6 +187,24 @@ public: m_map.emplace(std::make_pair(Vec2crd(pt->x()>>m_grid_log2, pt->y()>>m_grid_log2), std::move(value))); } + // Erase a data point equal to value. (ValueType has to declare the operator==). + // Returns true if the data point equal to value was found and removed. + bool erase(const ValueType &value) { + const Point *pt = m_point_accessor(value); + if (pt != nullptr) { + // Range of fragment starts around grid_corner, close to pt. + auto range = m_map.equal_range(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2)); + // Remove the first item. + for (auto it = range.first; it != range.second; ++ it) { + if (it->second == value) { + m_map.erase(it); + return true; + } + } + } + return false; + } + // Return a pair of std::pair find(const Vec2crd &pt) { // Iterate over 4 closest grid cells around pt, @@ -214,7 +232,7 @@ public: } } } - return (value_min != nullptr && dist_min < coordf_t(m_search_radius * m_search_radius)) ? + return (value_min != nullptr && dist_min < coordf_t(m_search_radius) * coordf_t(m_search_radius)) ? std::make_pair(value_min, dist_min) : std::make_pair(nullptr, std::numeric_limits::max()); } diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 925b88aca..e17fafd62 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -81,8 +81,8 @@ extern BoundingBox get_extents(const Polylines &polylines); inline double total_length(const Polylines &polylines) { double total = 0; - for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) - total += it->length(); + for (const Polyline &pl : polylines) + total += pl.length(); return total; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index fb6d90d60..e382c7dbc 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -281,16 +281,17 @@ bool Print::is_step_done(PrintObjectStep step) const std::vector Print::object_extruders() const { std::vector extruders; + extruders.reserve(m_regions.size() * 3); - for (PrintRegion* region : m_regions) { + for (const PrintRegion *region : m_regions) { // these checks reflect the same logic used in the GUI for enabling/disabling // extruder selection fields if (region->config().perimeters.value > 0 || m_config.brim_width.value > 0) - extruders.push_back(region->config().perimeter_extruder - 1); + extruders.emplace_back(region->config().perimeter_extruder - 1); if (region->config().fill_density.value > 0) - extruders.push_back(region->config().infill_extruder - 1); + extruders.emplace_back(region->config().infill_extruder - 1); if (region->config().top_solid_layers.value > 0 || region->config().bottom_solid_layers.value > 0) - extruders.push_back(region->config().solid_infill_extruder - 1); + extruders.emplace_back(region->config().solid_infill_extruder - 1); } sort_remove_duplicates(extruders); @@ -480,14 +481,6 @@ bool Print::apply_config(DynamicPrintConfig config) PrintObjectConfig new_config = this->default_object_config(); // we override the new config with object-specific options normalize_and_apply_config(new_config, object->model_object()->config); - // Force a refresh of a variable layer height profile at the PrintObject if it is not valid. - if (! object->layer_height_profile_valid) { - // The layer_height_profile is not valid for some reason (updated by the user or invalidated due to some option change). - // Invalidate the slicing step, which in turn invalidates everything. - object->invalidate_step(posSlice); - // Trigger recalculation. - invalidated = true; - } // check whether the new config is different from the current one t_config_option_keys diff = object->config().diff(new_config); object->config_apply_only(new_config, diff, true); @@ -567,8 +560,7 @@ exit_for_rearrange_regions: // Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads. for (PrintObject *object : m_objects) - if (! object->layer_height_profile_valid) - object->update_layer_height_profile(); + object->update_layer_height_profile(); return invalidated; } @@ -1141,6 +1133,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // Always make sure that the layer_height_profiles are set, as they should not be modified from the worker threads. for (PrintObject *object : m_objects) if (! object->layer_height_profile_valid) + // No need to call the next line as the step should already be invalidated above. + // update_apply_status(object->invalidate_step(posSlice)); object->update_layer_height_profile(); //FIXME there may be a race condition with the G-code export running at the background thread. @@ -1165,6 +1159,7 @@ bool Print::has_skirt() const || this->has_infinite_skirt(); } +// Precondition: Print::validate() requires the Print::apply() to be called its invocation. std::string Print::validate() const { if (m_objects.empty()) @@ -1253,12 +1248,10 @@ std::string Print::validate() const return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance"); if (! equal_layering(slicing_params, slicing_params0)) return L("The Wipe Tower is only supported for multiple objects if they are sliced equally."); - bool was_layer_height_profile_valid = object->layer_height_profile_valid; - object->update_layer_height_profile(); - object->layer_height_profile_valid = was_layer_height_profile_valid; if ( m_config.variable_layer_height ) { // comparing layer height profiles bool failed = false; + // layer_height_profile should be set by Print::apply(). if (tallest_object->layer_height_profile.size() >= object->layer_height_profile.size() ) { int i = 0; while ( i < object->layer_height_profile.size() && i < tallest_object->layer_height_profile.size()) { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 872ce31db..529608445 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -78,7 +78,6 @@ private: // Prevents erroneous use by other classes. public: // vector of (vectors of volume ids), indexed by region_id std::vector> region_volumes; - t_layer_height_ranges layer_height_ranges; // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. // The pairs of are packed into a 1D array to simplify handling by the Perl XS. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 10f5a1ac2..4b7a8906f 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -65,7 +65,6 @@ PrintObject::PrintObject(Print* print, ModelObject* model_object, bool add_insta this->set_copies(copies); } - this->layer_height_ranges = model_object->layer_height_ranges; this->layer_height_profile = model_object->layer_height_profile; } @@ -1109,7 +1108,7 @@ void PrintObject::discover_vertical_shells() #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); + shell = offset(offset_ex(union_ex(shell), - 0.5f * min_perimeter_infill_spacing), 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else @@ -1330,7 +1329,7 @@ bool PrintObject::update_layer_height_profile(std::vector &layer_heigh bool updated = false; // If the layer height profile is not set, try to use the one stored at the ModelObject. - if (layer_height_profile.empty() && layer_height_profile.data() != this->model_object()->layer_height_profile.data()) { + if (layer_height_profile.empty()) { layer_height_profile = this->model_object()->layer_height_profile; updated = true; } @@ -1347,10 +1346,9 @@ bool PrintObject::update_layer_height_profile(std::vector &layer_heigh if (layer_height_profile.empty()) { if (0) // if (this->layer_height_profile.empty()) - layer_height_profile = layer_height_profile_adaptive(slicing_params, this->layer_height_ranges, - this->model_object()->volumes); + layer_height_profile = layer_height_profile_adaptive(slicing_params, this->model_object()->layer_height_ranges, this->model_object()->volumes); else - layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->layer_height_ranges); + layer_height_profile = layer_height_profile_from_ranges(slicing_params, this->model_object()->layer_height_ranges); updated = true; } return updated; diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index d85cf334b..28e6e1018 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace Slic3r { @@ -125,8 +126,13 @@ void AppConfig::load() void AppConfig::save() { + // The config is first written to a file with a PID suffix and then moved + // to avoid race conditions with multiple instances of Slic3r + const auto path = config_path(); + std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); + boost::nowide::ofstream c; - c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc); + c.open(path_pid, std::ios::out | std::ios::trunc); c << "# " << Slic3r::header_slic3r_generated() << std::endl; // Make sure the "no" category is written first. for (const std::pair &kvp : m_storage[""]) @@ -155,6 +161,8 @@ void AppConfig::save() } } c.close(); + + rename_file(path_pid, path); m_dirty = false; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5dc685111..ae5743f67 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -323,17 +323,17 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : double brim_width = config->opt_float("brim_width"); if (boost::any_cast(value) == true) { - new_val = m_brim_width == 0.0 ? 10 : + new_val = m_brim_width == 0.0 ? 5 : m_brim_width < 0.0 ? m_brim_width * (-1) : m_brim_width; } - else{ + else { m_brim_width = brim_width * (-1); new_val = 0; } new_conf.set_key_value("brim_width", new ConfigOptionFloat(new_val)); } - else{ //(opt_key == "support") + else { //(opt_key == "support") const wxString& selection = boost::any_cast(value); auto support_material = selection == _("None") ? false : true; From 856d9838639e8cf432ce3ad719ee4bcc679a7540 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Dec 2018 17:48:17 +0100 Subject: [PATCH 24/61] removing global variable for cancel checking --- src/libslic3r/SLA/SLABasePool.cpp | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 6f28caf71..0e7dd3d06 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -10,10 +10,6 @@ namespace Slic3r { namespace sla { -namespace { -ThrowOnCancel throw_on_cancel = [](){}; -} - /// Convert the triangulation output to an intermediate mesh. Contour3D convert(const Polygons& triangles, coord_t z, bool dir) { @@ -35,7 +31,9 @@ Contour3D convert(const Polygons& triangles, coord_t z, bool dir) { } Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, - double floor_z_mm, double ceiling_z_mm) { + double floor_z_mm, double ceiling_z_mm, + ThrowOnCancel thr) +{ using std::transform; using std::back_inserter; ExPolygon poly; @@ -64,8 +62,6 @@ Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling, }); }; - auto& thr = throw_on_cancel; - std::for_each(tri.begin(), tri.end(), [&rp, &rpi, thr, &idx, is_upper, fz, cz](const Polygon& pp) { @@ -221,11 +217,12 @@ inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { template Contour3D round_edges(const ExPolygon& base_plate, - double radius_mm, - double degrees, - double ceilheight_mm, - bool dir, - ExP&& last_offset = ExP(), D&& last_height = D()) + double radius_mm, + double degrees, + double ceilheight_mm, + bool dir, + ThrowOnCancel throw_on_cancel, + ExP&& last_offset = ExP(), D&& last_height = D()) { auto ob = base_plate; auto ob_prev = ob; @@ -252,7 +249,7 @@ Contour3D round_edges(const ExPolygon& base_plate, wh = ceilheight_mm - radius_mm + stepy; Contour3D pwalls; - pwalls = walls(ob, ob_prev, wh, wh_prev); + pwalls = walls(ob, ob_prev, wh, wh_prev, throw_on_cancel); curvedwalls.merge(pwalls); ob_prev = ob; @@ -275,7 +272,7 @@ Contour3D round_edges(const ExPolygon& base_plate, wh = ceilheight_mm - radius_mm - stepy; Contour3D pwalls; - pwalls = walls(ob_prev, ob, wh_prev, wh); + pwalls = walls(ob_prev, ob, wh_prev, wh, throw_on_cancel); curvedwalls.merge(pwalls); ob_prev = ob; @@ -359,7 +356,8 @@ inline Point centroid(const ExPolygon& poly) { /// with explicit bridges. Bridges are generated from each shape's centroid /// to the center of the "scene" which is the centroid calculated from the shape /// centroids (a star is created...) -ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50) +ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, + ThrowOnCancel throw_on_cancel = [](){}) { namespace bgi = boost::geometry::index; using SpatElement = std::pair; @@ -394,7 +392,8 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50) idx = 0; std::transform(centroids.begin(), centroids.end(), std::back_inserter(punion), - [&punion, &boxindex, cc, max_dist_mm, &idx](const Point& c) + [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] + (const Point& c) { throw_on_cancel(); double dx = x(c) - x(cc), dy = y(c) - y(cc); @@ -455,12 +454,10 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, const PoolConfig& cfg) { - throw_on_cancel = cfg.throw_on_cancel; - double mdist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm) + cfg.max_merge_distance_mm; - auto concavehs = concave_hull(ground_layer, mdist); + auto concavehs = concave_hull(ground_layer, mdist, cfg.throw_on_cancel); for(ExPolygon& concaveh : concavehs) { if(concaveh.contour.points.empty()) return; concaveh.holes.clear(); @@ -518,6 +515,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, phi, // 170 degrees 0, // z position of the input plane true, + cfg.throw_on_cancel, ob, wh); pool.merge(curvedwalls); @@ -525,7 +523,8 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, ExPolygon ob_contr = ob; ob_contr.holes.clear(); - auto pwalls = walls(ob_contr, inner_base, wh, -cfg.min_wall_height_mm); + auto pwalls = walls(ob_contr, inner_base, wh, -cfg.min_wall_height_mm, + cfg.throw_on_cancel); pool.merge(pwalls); Polygons top_triangles, bottom_triangles; @@ -541,6 +540,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, 90, // 90 degrees 0, // z position of the input plane false, + cfg.throw_on_cancel, ob, wh); pool.merge(curvedwalls); From 09c539a24233a0ff41b54720870fec3725f130d7 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Dec 2018 17:49:31 +0100 Subject: [PATCH 25/61] Trigger background processing update when switching to a preview tab. Implements "Go Direct to the preview screen after slicing #152" --- src/libslic3r/Print.hpp | 3 +++ src/libslic3r/PrintBase.hpp | 2 ++ src/libslic3r/SLAPrint.cpp | 12 ++++++++++++ src/libslic3r/SLAPrint.hpp | 3 +++ src/slic3r/GUI/BackgroundSlicingProcess.hpp | 5 +++++ src/slic3r/GUI/Plater.cpp | 3 ++- xs/xsp/Print.xsp | 5 ----- 7 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 529608445..04de0f95c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -297,7 +297,10 @@ public: // methods for handling state bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); } + // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(PrintObjectStep step) const; + // Returns true if the last step was finished with success. + bool finished() const override { return this->is_step_done(psGCodeExport); } bool has_infinite_skirt() const; bool has_skirt() const; diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index ca4d4eaf3..401718773 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -285,6 +285,8 @@ public: void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; } // Cancel the running computation. Stop execution of all the background threads. void restart() { m_cancel_status = NOT_CANCELED; } + // Returns true if the last step was finished with success. + virtual bool finished() const = 0; const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 239c86eca..07de3596a 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -936,6 +936,18 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vectorstate_mutex()); + for (const SLAPrintObject *object : m_objects) + if (! object->m_state.is_done_unguarded(step)) + return false; + return true; +} + SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): Inherited(print, model_object), m_stepmask(slaposCount, true), diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index ebdc3f854..d74294761 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -185,6 +185,9 @@ public: bool empty() const override { return m_objects.empty(); } ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; void process() override; + bool is_step_done(SLAPrintObjectStep step) const; + // Returns true if the last step was finished with success. + bool finished() const override { return this->is_step_done(slaposIndexSlices); } template void export_raster(const std::string& fname) { if(m_printer) m_printer->save(fname); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 981bf6bbf..bb072fb98 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -110,6 +110,11 @@ public: State state() const { return m_state; } bool idle() const { return m_state == STATE_IDLE; } bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; } + // Returns true if the last step of the active print was finished with success. + // The "finished" flag is reset by the apply() method, if it changes the state of the print. + // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs), + // and it does not account for the OctoPrint scheduling. + bool finished() const { return m_print->finished(); } private: void thread_proc(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ae5743f67..af8769814 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2212,6 +2212,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) } else if (current_panel == preview) { + this->q->reslice(); preview->reload_print(); preview->set_canvas_as_dirty(); } @@ -3064,7 +3065,7 @@ void Plater::reslice() #else this->p->canvas3D->reload_scene(false); #endif // ENABLE_REMOVE_TABS_FROM_PLATER - if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && !this->p->background_process.running()) { + if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) == 0 && !this->p->background_process.running() && !this->p->background_process.finished()) { // The print is valid and it can be started. if (this->p->background_process.start()) this->p->statusbar()->set_cancel_callback([this]() { diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 5d17ac258..aa90a3ad9 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -49,8 +49,6 @@ _constant() Ref config() %code%{ RETVAL = &THIS->config(); %}; Points copies(); - t_layer_height_ranges layer_height_ranges() - %code%{ RETVAL = THIS->layer_height_ranges; %}; std::vector layer_height_profile() %code%{ RETVAL = THIS->layer_height_profile; %}; Clone bounding_box(); @@ -58,9 +56,6 @@ _constant() Points _shifted_copies() %code%{ RETVAL = THIS->copies(); %}; - void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) - %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; - size_t layer_count(); Ref get_layer(int idx); From 0f8658154b9c0f0d54566f71183b593463972198 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 11 Dec 2018 17:55:15 +0100 Subject: [PATCH 26/61] Fixing the crash with large model. --- src/libslic3r/SLA/SLASupportTree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 6cab763cd..1914bed93 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1370,7 +1370,7 @@ bool SLASupportTree::generate(const PointSet &points, add_base(cfg.base_height_mm, cfg.base_radius_mm); // connects to ground, eligible for bridging - cl_centroids.emplace_back(sidehead.id); + cl_centroids.emplace_back(c); } else { // Creating the bridge to the nearest pillar From 6e30c60afc98630c7cde389fe64c6fd3aa87ca0d Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 11 Dec 2018 18:56:23 +0100 Subject: [PATCH 27/61] Fixed compilation on OSX. --- src/libslic3r/Point.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 82df93cc1..d92667362 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -193,7 +193,7 @@ public: const Point *pt = m_point_accessor(value); if (pt != nullptr) { // Range of fragment starts around grid_corner, close to pt. - auto range = m_map.equal_range(Point(pt->x>>m_grid_log2, pt->y>>m_grid_log2)); + auto range = m_map.equal_range(Point((*pt)(0)>>m_grid_log2, (*pt)(1)>>m_grid_log2)); // Remove the first item. for (auto it = range.first; it != range.second; ++ it) { if (it->second == value) { From a57ff1c221b43094bfb33d2e00b4b080f7cc545b Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Dec 2018 08:40:10 +0100 Subject: [PATCH 28/61] Fixed DnD under all platforms + try to fix the work of Backspase in ObjectList (using EVT_KEY_DOWN instead of EVT_CHAR) --- src/slic3r/GUI/GUI_ObjectList.cpp | 53 +++++++++++++------------------ src/slic3r/GUI/GUI_ObjectList.hpp | 21 ++++++++++++ src/slic3r/GUI/wxExtensions.cpp | 7 +++- src/slic3r/GUI/wxExtensions.hpp | 3 +- 4 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index ba98573bf..e3e53a5e9 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -78,6 +78,10 @@ ObjectList::ObjectList(wxWindow* parent) : Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); + +#ifdef __WXOSX__ + Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); +#endif //__WXOSX__ } ObjectList::~ObjectList() @@ -93,10 +97,6 @@ void ObjectList::create_objects_ctrl() // 2. change it to the normal min value (200) after first whole App updating/layouting SetMinSize(wxSize(-1, 1500)); // #ys_FIXME -#ifdef __WXOSX__ - Connect(wxEVT_CHAR, wxKeyEventHandler(ObjectList::OnChar), NULL, this); -#endif //__WXOSX__ - m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer->Add(this, 1, wxGROW | wxLEFT, 20); @@ -337,14 +337,15 @@ void ObjectList::selection_changed() void ObjectList::OnChar(wxKeyEvent& event) { - if (event.GetKeyCode() == WXK_DELETE && event.GetKeyCode() == WXK_BACK){ + printf("KeyDown event\n"); + if (event.GetKeyCode() == WXK_BACK){ printf("WXK_BACK\n"); remove(); } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) select_item_all_children(); - else - event.Skip(); + + event.Skip(); } void ObjectList::OnContextMenu(wxDataViewEvent&) @@ -424,12 +425,6 @@ void ObjectList::key_event(wxKeyEvent& event) event.Skip(); } -struct draging_item_data -{ - int obj_idx; - int vol_idx; -}; - void ObjectList::OnBeginDrag(wxDataViewEvent &event) { wxDataViewItem item(event.GetItem()); @@ -449,10 +444,10 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) **/ m_prevent_list_events = true;//it's needed for GTK - wxTextDataObject *obj = new wxTextDataObject; - obj->SetText(wxString::Format("%d", m_objects_model->GetVolumeIdByItem(item))); - event.SetDataObject(obj); - event.SetDragFlags(/*wxDrag_AllowMove*/wxDrag_DefaultMove); // allows both copy and move; + m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), m_objects_model->GetVolumeIdByItem(item)); + + event.SetDataObject(new wxTextDataObject); + event.SetDragFlags(wxDrag_DefaultMove); // allows both copy and move; } void ObjectList::OnDropPossible(wxDataViewEvent &event) @@ -460,8 +455,10 @@ void ObjectList::OnDropPossible(wxDataViewEvent &event) wxDataViewItem item(event.GetItem()); // only allow drags for item or background, not containers - if (event.GetDataFormat() != wxDF_UNICODETEXT || item.IsOk() && - (m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->GetItemType(item) != itVolume)) + if (item.IsOk() && + (m_objects_model->GetParent(item) == wxDataViewItem(0) || + m_objects_model->GetItemType(item) != itVolume || + m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item))) event.Veto(); } @@ -469,20 +466,16 @@ void ObjectList::OnDrop(wxDataViewEvent &event) { wxDataViewItem item(event.GetItem()); - if (m_selected_object_id < 0 || event.GetDataFormat() != wxDF_UNICODETEXT || - item.IsOk() && ( m_objects_model->GetParent(item) == wxDataViewItem(0) || - m_objects_model->GetItemType(item) != itVolume) ) { + if (item.IsOk() && ( m_objects_model->GetParent(item) == wxDataViewItem(0) || + m_objects_model->GetItemType(item) != itVolume) || + m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) { event.Veto(); + m_dragged_data.clear(); return; } - wxTextDataObject obj; - obj.SetData(wxDF_UNICODETEXT, event.GetDataSize(), event.GetDataBuffer()); - printf("Drop\n"); - - int from_volume_id = std::stoi(obj.GetText().ToStdString()); + const int from_volume_id = m_dragged_data.vol_idx(); int to_volume_id = m_objects_model->GetVolumeIdByItem(item); - printf("from %d to %d\n", from_volume_id, to_volume_id); #ifdef __WXGTK__ /* Under GTK, DnD moves an item between another two items. @@ -497,16 +490,14 @@ void ObjectList::OnDrop(wxDataViewEvent &event) int cnt = 0; for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) std::swap(volumes[id], volumes[id + delta]); - printf("Volumes are swapped\n"); select_item(m_objects_model->ReorganizeChildren(from_volume_id, to_volume_id, m_objects_model->GetParent(item))); - printf("ItemChildren are Reorganized\n"); m_parts_changed = true; parts_changed(m_selected_object_id); - printf("DropCompleted\n"); + m_dragged_data.clear(); // m_prevent_list_events = false; } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 5cb76b8aa..33e3894fe 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -53,6 +53,27 @@ struct ItemForDelete class ObjectList : public wxDataViewCtrl { + + struct dragged_item_data + { + void init(const int obj_idx, const int vol_idx) { + m_obj_idx = obj_idx; + m_vol_idx = vol_idx; + } + + void clear() { + m_obj_idx = -1; + m_vol_idx = -1; + } + + int obj_idx() const { return m_obj_idx; } + int vol_idx() const { return m_vol_idx; } + + private: + int m_obj_idx = -1; + int m_vol_idx = -1; + } m_dragged_data; + wxBoxSizer *m_sizer {nullptr}; DynamicPrintConfig *m_default_config {nullptr}; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 98a246f10..57828ea9a 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -891,7 +891,7 @@ wxDataViewItem PrusaObjectDataViewModel::GetItemByInstanceId(int obj_idx, int in return wxDataViewItem(0); } -int PrusaObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) +int PrusaObjectDataViewModel::GetIdByItem(const wxDataViewItem& item) const { wxASSERT(item.IsOk()); @@ -913,6 +913,11 @@ int PrusaObjectDataViewModel::GetIdByItemAndType(const wxDataViewItem& item, con return node->GetIdx(); } +int PrusaObjectDataViewModel::GetObjectIdByItem(const wxDataViewItem& item) const +{ + return GetIdByItem(GetTopParent(item)); +} + int PrusaObjectDataViewModel::GetVolumeIdByItem(const wxDataViewItem& item) const { return GetIdByItemAndType(item, itVolume); diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 6f87579e2..866317e25 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -457,8 +457,9 @@ public: wxDataViewItem GetItemById(int obj_idx); wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx); wxDataViewItem GetItemByInstanceId(int obj_idx, int inst_idx); - int GetIdByItem(const wxDataViewItem& item); + int GetIdByItem(const wxDataViewItem& item) const; int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const; + int GetObjectIdByItem(const wxDataViewItem& item) const; int GetVolumeIdByItem(const wxDataViewItem& item) const; int GetInstanceIdByItem(const wxDataViewItem& item) const; void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx); From 5ea8df0ca0f1bdfc044ead423a54f5ddb48b7414 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 10:02:01 +0100 Subject: [PATCH 29/61] Manual merge of the TriangleMesh.cpp from the stable branch. --- src/libslic3r/SLAPrint.hpp | 1 + src/libslic3r/TriangleMesh.cpp | 621 +++++++++++++++++++++------------ xs/t/01_trianglemesh.t | 8 +- 3 files changed, 413 insertions(+), 217 deletions(-) diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index d74294761..3bc3a20c0 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -185,6 +185,7 @@ public: bool empty() const override { return m_objects.empty(); } ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; void process() override; + // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(SLAPrintObjectStep step) const; // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposIndexSlices); } diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 0da2334f8..3d97f8006 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1212,6 +1212,345 @@ static inline void remove_tangent_edges(std::vector &lines) } } + +struct OpenPolyline { + OpenPolyline() {}; + OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : + start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } + void reverse() { + std::swap(start, end); + std::reverse(points.begin(), points.end()); + } + IntersectionReference start; + IntersectionReference end; + Points points; + double length; + bool consumed; +}; + +// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. +// Only connects segments crossing triangles of the same orientation. +static void chain_lines_by_triangle_connectivity(std::vector &lines, Polygons &loops, std::vector &open_polylines) +{ + // Build a map of lines by edge_a_id and a_id. + std::vector by_edge_a_id; + std::vector by_a_id; + by_edge_a_id.reserve(lines.size()); + by_a_id.reserve(lines.size()); + for (IntersectionLine &line : lines) { + if (! line.skip()) { + if (line.edge_a_id != -1) + by_edge_a_id.emplace_back(&line); + if (line.a_id != -1) + by_a_id.emplace_back(&line); + } + } + auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; + auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; + std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); + std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); + // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. + IntersectionLines::iterator it_line_seed = lines.begin(); + for (;;) { + // take first spare line and start a new loop + IntersectionLine *first_line = nullptr; + for (; it_line_seed != lines.end(); ++ it_line_seed) + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { + first_line = &(*it_line_seed ++); + break; + } + if (first_line == nullptr) + break; + first_line->set_skip(); + Points loop_pts; + loop_pts.emplace_back(first_line->a); + IntersectionLine *last_line = first_line; + + /* + printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, + first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); + */ + + IntersectionLine key; + for (;;) { + // find a line starting where last one finishes + IntersectionLine* next_line = nullptr; + if (last_line->edge_b_id != -1) { + key.edge_a_id = last_line->edge_b_id; + auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); + if (it_begin != by_edge_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr && last_line->b_id != -1) { + key.a_id = last_line->b_id; + auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); + if (it_begin != by_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr) { + // Check whether we closed this loop. + if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || + (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { + // The current loop is complete. Add it to the output. + loops.emplace_back(std::move(loop_pts)); + #ifdef SLIC3R_TRIANGLEMESH_DEBUG + printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); + #endif + } else { + // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. + loop_pts.emplace_back(last_line->b); + open_polylines.emplace_back(OpenPolyline( + IntersectionReference(first_line->a_id, first_line->edge_a_id), + IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); + } + break; + } + /* + printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, + next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); + */ + loop_pts.emplace_back(next_line->a); + last_line = next_line; + next_line->set_skip(); + } + } +} + +std::vector open_polylines_sorted(std::vector &open_polylines, bool update_lengths) +{ + std::vector out; + out.reserve(open_polylines.size()); + for (OpenPolyline &opl : open_polylines) + if (! opl.consumed) { + if (update_lengths) + opl.length = Slic3r::length(opl.points); + out.emplace_back(&opl); + } + std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); + return out; +} + +// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) +{ + // Store the end points of open_polylines into vectors sorted + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } + // Return a unique ID for the intersection point. + // Return a positive id for a point, or a negative id for an edge. + int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; + std::vector by_id; + by_id.reserve(2 * open_polylines.size()); + for (OpenPolyline &opl : open_polylines) { + if (opl.start.point_id != -1 || opl.start.edge_id != -1) + by_id.emplace_back(OpenPolylineEnd(&opl, true)); + if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) + by_id.emplace_back(OpenPolylineEnd(&opl, false)); + } + std::sort(by_id.begin(), by_id.end(), by_id_lower); + // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). + auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { + for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + it != by_id.end() && it->id() == end.id(); ++ it) + if (*it == end) + return it; + return by_id.end(); + }; + // Try to connect the loops. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, false); + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + opl->consumed = true; + OpenPolylineEnd end(opl, false); + for (;;) { + // find a line starting where last one finishes + auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) + if (! it_next_start->polyline->consumed) + goto found; + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + break; + found: + // Attach this polyline to the end of the initial polyline. + if (it_next_start->start) { + auto it = it_next_start->polyline->points.begin(); + std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = it_next_start->polyline->points.rbegin(); + std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); + } + opl->length += it_next_start->polyline->length; + // Mark the next polyline as consumed. + it_next_start->polyline->points.clear(); + it_next_start->polyline->length = 0.; + it_next_start->polyline->consumed = true; + if (try_connect_reversed) { + // Running in a mode, where the polylines may be connected by mixing their orientations. + // Update the end point lookup structure after the end point of the current polyline was extended. + auto it_end = find_polyline_end(end); + auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); + // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. + std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); + // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. + std::swap(*it_end, *it_next_end); + } + // Check whether we closed this loop. + if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || + (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { + // The current loop is complete. Add it to the output. + //assert(opl->points.front().point_id == opl->points.back().point_id); + //assert(opl->points.front().edge_id == opl->points.back().edge_id); + // Remove the duplicate last point. + opl->points.pop_back(); + if (opl->points.size() >= 3) { + if (try_connect_reversed && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + break; + } + // Continue with the current loop. + } + } +} + +// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices, +// possibly closing small gaps. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) +{ + const coord_t max_gap_scaled = (coord_t)scale_(max_gap); + + // Sort the open polylines by their length, so the new loops will be seeded from longer chains. + // Update the polyline lengths, return only not yet consumed polylines. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, true); + + // Store the end points of open_polylines into ClosestPointInRadiusLookup. + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + struct OpenPolylineEndAccessor { + const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); + for (OpenPolyline *opl : sorted_by_length) { + closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); + if (try_connect_reversed) + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + } + // Try to connect the loops. + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + OpenPolylineEnd end(opl, false); + if (try_connect_reversed) + // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. + closest_end_point_lookup.erase(end); + opl->consumed = true; + size_t n_segments_joined = 1; + for (;;) { + // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). + std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); + const OpenPolylineEnd *next_start = next_start_and_dist.first; + // Check whether we closed this loop. + double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast().squaredNorm(); + bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); + if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { + // Heuristics to decide, whether to close the loop, or connect another polyline. + // One should avoid closing loops shorter than max_gap_scaled. + loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); + } + if (loop_closed) { + // Remove the start point of the current polyline from the lookup. + // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. + opl->consumed = false; + closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); + if (current_loop_closing_distance2 == 0.) { + // Remove the duplicate last point. + opl->points.pop_back(); + } else { + // The end points are different, keep both of them. + } + if (opl->points.size() >= 3) { + if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + opl->consumed = true; + break; + } + if (next_start == nullptr) { + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + if (try_connect_reversed) + // Re-insert the end point. + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + break; + } + // Attach this polyline to the end of the initial polyline. + if (next_start->start) { + auto it = next_start->polyline->points.begin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = next_start->polyline->points.rbegin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); + } + ++ n_segments_joined; + // Remove the end points of the consumed polyline segment from the lookup. + OpenPolyline *opl2 = next_start->polyline; + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); + if (try_connect_reversed) + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); + opl2->points.clear(); + opl2->consumed = true; + // Continue with the current loop. + } + } +} + void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { #if 0 @@ -1221,231 +1560,83 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo assert(l.a != l.b); #endif /* _DEBUG */ - remove_tangent_edges(lines); + // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane, + // only the bottom triangle is considered to be cutting the plane. +// remove_tangent_edges(lines); - struct OpenPolyline { - OpenPolyline() {}; - OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : - start(start), end(end), points(std::move(points)), consumed(false) {} - void reverse() { - std::swap(start, end); - std::reverse(points.begin(), points.end()); +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + BoundingBox bbox_svg; + { + static int iRun = 0; + for (const Line &line : lines) { + bbox_svg.merge(line.a); + bbox_svg.merge(line.b); + } + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg); + for (const Line &line : lines) + svg.draw(line); + svg.Close(); } - IntersectionReference start; - IntersectionReference end; - Points points; - bool consumed; - }; +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + std::vector open_polylines; - { - // Build a map of lines by edge_a_id and a_id. - std::vector by_edge_a_id; - std::vector by_a_id; - by_edge_a_id.reserve(lines.size()); - by_a_id.reserve(lines.size()); - for (IntersectionLine &line : lines) { - if (! line.skip()) { - if (line.edge_a_id != -1) - by_edge_a_id.emplace_back(&line); - if (line.a_id != -1) - by_a_id.emplace_back(&line); - } + chain_lines_by_triangle_connectivity(lines, *loops, open_polylines); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) + svg.draw(Polyline(pl.points), "red"); + svg.Close(); } - auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; - auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; - std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); - std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); - // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. - IntersectionLines::iterator it_line_seed = lines.begin(); - for (;;) { - // take first spare line and start a new loop - IntersectionLine *first_line = nullptr; - for (; it_line_seed != lines.end(); ++ it_line_seed) - if (it_line_seed->is_seed_candidate()) { - //if (! it_line_seed->skip()) { - first_line = &(*it_line_seed ++); - break; - } - if (first_line == nullptr) - break; - first_line->set_skip(); - Points loop_pts; - loop_pts.emplace_back(first_line->a); - IntersectionLine *last_line = first_line; - - /* - printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, - first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); - */ - - IntersectionLine key; - for (;;) { - // find a line starting where last one finishes - IntersectionLine* next_line = nullptr; - if (last_line->edge_b_id != -1) { - key.edge_a_id = last_line->edge_b_id; - auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); - if (it_begin != by_edge_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr && last_line->b_id != -1) { - key.a_id = last_line->b_id; - auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); - if (it_begin != by_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr) { - // Check whether we closed this loop. - if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || - (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { - // The current loop is complete. Add it to the output. - loops->emplace_back(std::move(loop_pts)); - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); - #endif - } else { - // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. - loop_pts.emplace_back(last_line->b); - open_polylines.emplace_back(OpenPolyline( - IntersectionReference(first_line->a_id, first_line->edge_a_id), - IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); - } - break; - } - /* - printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, - next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); - */ - loop_pts.emplace_back(next_line->a); - last_line = next_line; - next_line->set_skip(); - } - } - } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Now process the open polylines. - if (! open_polylines.empty()) { - // Store the end points of open_polylines into vectors sorted - struct OpenPolylineEnd { - OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} - OpenPolyline *polyline; - // Is it the start or end point? - bool start; - const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } - int point_id() const { return ipref().point_id; } - int edge_id () const { return ipref().edge_id; } - }; - auto by_edge_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.edge_id() < ope2.edge_id(); }; - auto by_point_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.point_id() < ope2.point_id(); }; - std::vector by_edge_id; - std::vector by_point_id; - by_edge_id.reserve(2 * open_polylines.size()); - by_point_id.reserve(2 * open_polylines.size()); - for (OpenPolyline &opl : open_polylines) { - if (opl.start.edge_id != -1) - by_edge_id .emplace_back(OpenPolylineEnd(&opl, true)); - if (opl.end.edge_id != -1) - by_edge_id .emplace_back(OpenPolylineEnd(&opl, false)); - if (opl.start.point_id != -1) - by_point_id.emplace_back(OpenPolylineEnd(&opl, true)); - if (opl.end.point_id != -1) - by_point_id.emplace_back(OpenPolylineEnd(&opl, false)); - } - std::sort(by_edge_id .begin(), by_edge_id .end(), by_edge_lower); - std::sort(by_point_id.begin(), by_point_id.end(), by_point_lower); + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. + chain_open_polylines_exact(open_polylines, *loops, false); + chain_open_polylines_exact(open_polylines, *loops, true); - // Try to connect the loops. - for (OpenPolyline &opl : open_polylines) { - if (opl.consumed) +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) continue; - opl.consumed = true; - OpenPolylineEnd end(&opl, false); - for (;;) { - // find a line starting where last one finishes - OpenPolylineEnd* next_start = nullptr; - if (end.edge_id() != -1) { - auto it_begin = std::lower_bound(by_edge_id.begin(), by_edge_id.end(), end, by_edge_lower); - if (it_begin != by_edge_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_id.end(), end, by_edge_lower); - for (auto it_edge = it_begin; it_edge != it_end; ++ it_edge) - if (! it_edge->polyline->consumed) { - next_start = &(*it_edge); - break; - } - } - } - if (next_start == nullptr && end.point_id() != -1) { - auto it_begin = std::lower_bound(by_point_id.begin(), by_point_id.end(), end, by_point_lower); - if (it_begin != by_point_id.end()) { - auto it_end = std::upper_bound(it_begin, by_point_id.end(), end, by_point_lower); - for (auto it_point = it_begin; it_point != it_end; ++ it_point) - if (! it_point->polyline->consumed) { - next_start = &(*it_point); - break; - } - } - } - if (next_start == nullptr) { - // The current loop could not be closed. Unmark the segment. - opl.consumed = false; - break; - } - // Attach this polyline to the end of the initial polyline. - if (next_start->start) { - auto it = next_start->polyline->points.begin(); - std::copy(++ it, next_start->polyline->points.end(), back_inserter(opl.points)); - //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.end()); - } else { - auto it = next_start->polyline->points.rbegin(); - std::copy(++ it, next_start->polyline->points.rend(), back_inserter(opl.points)); - //opl.points.insert(opl.points.back(), ++ it, next_start->polyline->points.rend()); - } - end = *next_start; - end.start = !end.start; - next_start->polyline->points.clear(); - next_start->polyline->consumed = true; - // Check whether we closed this loop. - const IntersectionReference &ip1 = opl.start; - const IntersectionReference &ip2 = end.ipref(); - if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || - (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { - // The current loop is complete. Add it to the output. - //assert(opl.points.front().point_id == opl.points.back().point_id); - //assert(opl.points.front().edge_id == opl.points.back().edge_id); - // Remove the duplicate last point. - opl.points.pop_back(); - if (opl.points.size() >= 3) { - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - double area = 0.; - for (size_t i = 0, j = opl.points.size() - 1; i < opl.points.size(); j = i ++) - area += double(opl.points[j](0) + opl.points[i](0)) * double(opl.points[i](1) - opl.points[j](1)); - if (area < 0) - std::reverse(opl.points.begin(), opl.points.end()); - loops->emplace_back(std::move(opl.points)); - } - opl.points.clear(); - break; - } - // Continue with the current loop. - } + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); } + svg.Close(); } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + // Try to close gaps. + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. + const double max_gap = 2.; //mm + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) + continue; + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); + } + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // Only used to cut the mesh into two halves. diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t index fd57cf805..4013a1f83 100644 --- a/xs/t/01_trianglemesh.t +++ b/xs/t/01_trianglemesh.t @@ -79,7 +79,9 @@ my $cube = { my $m = Slic3r::TriangleMesh->new; $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); $m->repair; - my @z = (0,2,4,8,6,8,10,12,14,16,18,20); + # The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be + # open intervals at the bottom end, closed at the top end. + my @z = (0.0001,2,4,8,6,8,10,12,14,16,18,20); my $result = $m->slice(\@z); my $SCALING_FACTOR = 0.000001; for my $i (0..$#z) { @@ -105,7 +107,9 @@ my $cube = { # this second test also checks that performing a second slice on a mesh after # a transformation works properly (shared_vertices is correctly invalidated); # at Z = -10 we have a bottom horizontal surface - my $slices = $m->slice([ -5, -10 ]); + # (The slice at zero height does not belong to the mesh, the slicing considers the vertical structures to be + # open intervals at the bottom end, closed at the top end, so the Z = -10 is shifted a bit up to get a valid slice). + my $slices = $m->slice([ -5, -10+0.00001 ]); is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a bottom tangent plane includes its area'; } } From 91102e2c9eb0724441a5cf39b590985e09543d4e Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 10:12:35 +0100 Subject: [PATCH 30/61] Merged improvements of supports from stable to master. --- src/libslic3r/SupportMaterial.cpp | 218 ++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/SupportMaterial.cpp b/src/libslic3r/SupportMaterial.cpp index 31305f332..8cdc0ebfd 100644 --- a/src/libslic3r/SupportMaterial.cpp +++ b/src/libslic3r/SupportMaterial.cpp @@ -458,6 +458,8 @@ Polygons collect_slices_outer(const Layer &layer) class SupportGridPattern { public: + // Achtung! The support_polygons need to be trimmed by trimming_polygons, otherwise + // the selection by island_samples (see the island_samples() method) will not work! SupportGridPattern( // Support islands, to be stretched into a grid. Already trimmed with min(lower_layer_offset, m_gap_xy) const Polygons &support_polygons, @@ -485,6 +487,18 @@ public: bbox.align_to_grid(grid_resolution); m_grid.set_bbox(bbox); m_grid.create(*m_support_polygons, grid_resolution); +#if 0 + if (m_grid.has_intersecting_edges()) { + // EdgeGrid fails to produce valid signed distance function for self-intersecting polygons. + m_support_polygons_rotated = simplify_polygons(*m_support_polygons); + m_support_polygons = &m_support_polygons_rotated; + m_grid.set_bbox(bbox); + m_grid.create(*m_support_polygons, grid_resolution); +// assert(! m_grid.has_intersecting_edges()); + printf("SupportGridPattern: fixing polygons with intersection %s\n", + m_grid.has_intersecting_edges() ? "FAILED" : "SUCCEEDED"); + } +#endif m_grid.calculate_sdf(); // Sample a single point per input support polygon, keep it as a reference to maintain corresponding // polygons if ever these polygons get split into parts by the trimming polygons. @@ -499,9 +513,12 @@ public: { // Generate islands, so each island may be tested for overlap with m_island_samples. assert(std::abs(2 * offset_in_grid) < m_grid.resolution()); - ExPolygons islands = diff_ex( - m_grid.contours_simplified(offset_in_grid, fill_holes), - *m_trimming_polygons, false); +#ifdef SLIC3R_DEBUG + Polygons support_polygons_simplified = m_grid.contours_simplified(offset_in_grid, fill_holes); + ExPolygons islands = diff_ex(support_polygons_simplified, *m_trimming_polygons, false); +#else + ExPolygons islands = diff_ex(m_grid.contours_simplified(offset_in_grid, fill_holes), *m_trimming_polygons, false); +#endif // Extract polygons, which contain some of the m_island_samples. Polygons out; @@ -551,7 +568,10 @@ public: bbox.merge(get_extents(islands)); if (!out.empty()) bbox.merge(get_extents(out)); + if (!support_polygons_simplified.empty()) + bbox.merge(get_extents(support_polygons_simplified)); SVG svg(debug_out_path("extract_support_from_grid_trimmed-%d.svg", iRun).c_str(), bbox); + svg.draw(union_ex(support_polygons_simplified), "gray", 0.25f); svg.draw(islands, "red", 0.5f); svg.draw(union_ex(out), "green", 0.5f); svg.draw(union_ex(*m_support_polygons), "blue", 0.5f); @@ -568,7 +588,121 @@ public: return out; } +#ifdef SLIC3R_DEBUG + void serialize(const std::string &path) + { + FILE *file = ::fopen(path.c_str(), "wb"); + ::fwrite(&m_support_spacing, 8, 1, file); + ::fwrite(&m_support_angle, 8, 1, file); + uint32_t n_polygons = m_support_polygons->size(); + ::fwrite(&n_polygons, 4, 1, file); + for (uint32_t i = 0; i < n_polygons; ++ i) { + const Polygon &poly = (*m_support_polygons)[i]; + uint32_t n_points = poly.size(); + ::fwrite(&n_points, 4, 1, file); + for (uint32_t j = 0; j < n_points; ++ j) { + const Point &pt = poly.points[j]; + ::fwrite(&pt.x, sizeof(coord_t), 1, file); + ::fwrite(&pt.y, sizeof(coord_t), 1, file); + } + } + n_polygons = m_trimming_polygons->size(); + ::fwrite(&n_polygons, 4, 1, file); + for (uint32_t i = 0; i < n_polygons; ++ i) { + const Polygon &poly = (*m_trimming_polygons)[i]; + uint32_t n_points = poly.size(); + ::fwrite(&n_points, 4, 1, file); + for (uint32_t j = 0; j < n_points; ++ j) { + const Point &pt = poly.points[j]; + ::fwrite(&pt.x, sizeof(coord_t), 1, file); + ::fwrite(&pt.y, sizeof(coord_t), 1, file); + } + } + ::fclose(file); + } + + static SupportGridPattern deserialize(const std::string &path, int which = -1) + { + SupportGridPattern out; + out.deserialize_(path, which); + return out; + } + + // Deserialization constructor + bool deserialize_(const std::string &path, int which = -1) + { + FILE *file = ::fopen(path.c_str(), "rb"); + if (file == nullptr) + return false; + + m_support_polygons = &m_support_polygons_deserialized; + m_trimming_polygons = &m_trimming_polygons_deserialized; + + ::fread(&m_support_spacing, 8, 1, file); + ::fread(&m_support_angle, 8, 1, file); + //FIXME + //m_support_spacing *= 0.01 / 2; + uint32_t n_polygons; + ::fread(&n_polygons, 4, 1, file); + m_support_polygons_deserialized.reserve(n_polygons); + int32_t scale = 1; + for (uint32_t i = 0; i < n_polygons; ++ i) { + Polygon poly; + uint32_t n_points; + ::fread(&n_points, 4, 1, file); + poly.points.reserve(n_points); + for (uint32_t j = 0; j < n_points; ++ j) { + coord_t x, y; + ::fread(&x, sizeof(coord_t), 1, file); + ::fread(&y, sizeof(coord_t), 1, file); + poly.points.emplace_back(Point(x * scale, y * scale)); + } + if (which == -1 || which == i) + m_support_polygons_deserialized.emplace_back(std::move(poly)); + printf("Polygon %d, area: %lf\n", i, area(poly.points)); + } + ::fread(&n_polygons, 4, 1, file); + m_trimming_polygons_deserialized.reserve(n_polygons); + for (uint32_t i = 0; i < n_polygons; ++ i) { + Polygon poly; + uint32_t n_points; + ::fread(&n_points, 4, 1, file); + poly.points.reserve(n_points); + for (uint32_t j = 0; j < n_points; ++ j) { + coord_t x, y; + ::fread(&x, sizeof(coord_t), 1, file); + ::fread(&y, sizeof(coord_t), 1, file); + poly.points.emplace_back(Point(x * scale, y * scale)); + } + m_trimming_polygons_deserialized.emplace_back(std::move(poly)); + } + ::fclose(file); + + m_support_polygons_deserialized = simplify_polygons(m_support_polygons_deserialized, false); + //m_support_polygons_deserialized = to_polygons(union_ex(m_support_polygons_deserialized, false)); + + // Create an EdgeGrid, initialize it with projection, initialize signed distance field. + coord_t grid_resolution = coord_t(scale_(m_support_spacing)); + BoundingBox bbox = get_extents(*m_support_polygons); + bbox.offset(20); + bbox.align_to_grid(grid_resolution); + m_grid.set_bbox(bbox); + m_grid.create(*m_support_polygons, grid_resolution); + m_grid.calculate_sdf(); + // Sample a single point per input support polygon, keep it as a reference to maintain corresponding + // polygons if ever these polygons get split into parts by the trimming polygons. + m_island_samples = island_samples(*m_support_polygons); + return true; + } + + const Polygons& support_polygons() const { return *m_support_polygons; } + const Polygons& trimming_polygons() const { return *m_trimming_polygons; } + const EdgeGrid::Grid& grid() const { return m_grid; } + +#endif /* SLIC3R_DEBUG */ + private: + SupportGridPattern() {} SupportGridPattern& operator=(const SupportGridPattern &rhs); #if 0 @@ -639,6 +773,12 @@ private: // Internal sample points of supporting expolygons. These internal points are used to pick regions corresponding // to the initial supporting regions, after these regions werre grown and possibly split to many by the trimming polygons. Points m_island_samples; + +#ifdef SLIC3R_DEBUG + // support for deserialization of m_support_polygons, m_trimming_polygons + Polygons m_support_polygons_deserialized; + Polygons m_trimming_polygons_deserialized; +#endif /* SLIC3R_DEBUG */ }; namespace SupportMaterialInternal { @@ -783,17 +923,40 @@ namespace SupportMaterialInternal { if (surface.surface_type == stBottomBridge && surface.bridge_angle != -1) polygons_append(bridges, surface.expolygon); //FIXME add the gap filled areas. Extrude the gaps with a bridge flow? - contact_polygons = diff(contact_polygons, bridges, true); - // Add the bridge anchors into the region. + // Remove the unsupported ends of the bridges from the bridged areas. //FIXME add supports at regular intervals to support long bridges! - polygons_append(contact_polygons, - intersection( + bridges = diff(bridges, // Offset unsupported edges into polygons. - offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), - bridges)); + offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + // Remove bridged areas from the supported areas. + contact_polygons = diff(contact_polygons, bridges, true); } } +#if 0 +static int Test() +{ +// for (int i = 0; i < 30; ++ i) + { + int i = -1; +// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000-prev.bin", i); +// SupportGridPattern grid("d:\\temp\\support-top-contacts-final-run1-layer460-z70.300000.bin", i); + auto grid = SupportGridPattern::deserialize("d:\\temp\\support-top-contacts-final-run1-layer27-z5.650000.bin", i); + std::vector> intersections = grid.grid().intersecting_edges(); + if (! intersections.empty()) + printf("Intersections between contours!\n"); + Slic3r::export_intersections_to_svg("d:\\temp\\support_polygon_intersections.svg", grid.support_polygons()); + Slic3r::SVG::export_expolygons("d:\\temp\\support_polygons.svg", union_ex(grid.support_polygons(), false)); + Slic3r::SVG::export_expolygons("d:\\temp\\trimming_polygons.svg", union_ex(grid.trimming_polygons(), false)); + Polygons extracted = grid.extract_support(scale_(0.21 / 2), true); + Slic3r::SVG::export_expolygons("d:\\temp\\extracted.svg", union_ex(extracted, false)); + printf("hu!"); + } + return 0; +} +static int run_support_test = Test(); +#endif /* SLIC3R_DEBUG */ + // Generate top contact layers supporting overhangs. // For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. // If supports over bed surface only are requested, don't generate contact layers over an object. @@ -1096,6 +1259,8 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ } } + // Achtung! The contact_polygons need to be trimmed by slices_margin_cached, otherwise + // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. contact_polygons, @@ -1114,9 +1279,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // Reduce the amount of dense interfaces: Do not generate dense interfaces below overhangs with 60% overhang of the extrusions. Polygons dense_interface_polygons = diff(overhang_polygons, offset2(lower_layer_polygons, - no_interface_offset * 0.5f, no_interface_offset * (0.6f + 0.5f), SUPPORT_SURFACES_OFFSET_PARAMETERS)); -// offset(lower_layer_polygons, no_interface_offset * 0.6f, SUPPORT_SURFACES_OFFSET_PARAMETERS)); if (! dense_interface_polygons.empty()) { - //FIXME do it for the bridges only? + dense_interface_polygons = + // Achtung! The dense_interface_polygons need to be trimmed by slices_margin_cached, otherwise + // the selection by island_samples (see the SupportGridPattern::island_samples() method) will not work! + diff( + // Regularize the contour. + offset(dense_interface_polygons, no_interface_offset * 0.1f), + slices_margin_cached); SupportGridPattern support_grid_pattern( // Support islands, to be stretched into a grid. dense_interface_polygons, @@ -1126,8 +1296,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), Geometry::deg2rad(m_object_config->support_material_angle.value)); new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5, false); + #ifdef SLIC3R_DEBUG + { + support_grid_pattern.serialize(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.bin", iRun, layer_id, layer.print_z)); + + BoundingBox bbox = get_extents(contact_polygons); + bbox.merge(get_extents(new_layer.polygons)); + ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final0-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z)); + svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f); + svg.draw(union_ex(contact_polygons, false), "blue", 0.5f); + svg.draw(union_ex(dense_interface_polygons, false), "green", 0.5f); + svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f); + svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f)); + } + #endif /* SLIC3R_DEBUG */ } } + #ifdef SLIC3R_DEBUG + { + BoundingBox bbox = get_extents(contact_polygons); + bbox.merge(get_extents(new_layer.polygons)); + ::Slic3r::SVG svg(debug_out_path("support-top-contacts-final-run%d-layer%d-z%f.svg", iRun, layer_id, layer.print_z)); + svg.draw(union_ex(*new_layer.contact_polygons, false), "gray", 0.5f); + svg.draw(union_ex(contact_polygons, false), "blue", 0.5f); + svg.draw(union_ex(overhang_polygons, false), "green", 0.5f); + svg.draw(union_ex(new_layer.polygons, true), "red", 0.5f); + svg.draw_outline(union_ex(new_layer.polygons, true), "black", "black", scale_(0.1f)); + } + #endif /* SLIC3R_DEBUG */ // Even after the contact layer was expanded into a grid, some of the contact islands may be too tiny to be extruded. // Remove those tiny islands from new_layer.polygons and new_layer.contact_polygons. From 2b9f52c33ccac21c0631765aca4493cd4bbdf145 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 10:29:42 +0100 Subject: [PATCH 31/61] Set a safety offset on a freshly sliced mesh to 0.0001 to satisfy GH #520, #1029, #1364 This change needs to be tested throughly on a large data set of meshes. --- src/libslic3r/TriangleMesh.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 3d97f8006..d02bfb284 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1771,10 +1771,11 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic // p_slices = diff(p_slices, *loop); //} - // perform a safety offset to merge very close facets (TODO: find test case for this) - double safety_offset = scale_(0.0499); -//FIXME see https://github.com/prusa3d/Slic3r/issues/520 -// double safety_offset = scale_(0.0001); + // Perform a safety offset to merge very close facets (TODO: find test case for this) + // 0.0499 comes from https://github.com/slic3r/Slic3r/issues/959 +// double safety_offset = scale_(0.0499); + // 0.0001 is set to satisfy GH #520, #1029, #1364 + double safety_offset = scale_(0.0001); /* The following line is commented out because it can generate wrong polygons, see for example issue #661 */ From ba4c8c1b87ce806564da5ade369d567eafff33df Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 12 Dec 2018 10:38:07 +0100 Subject: [PATCH 32/61] PrusaControl-like background color --- src/slic3r/GUI/GLCanvas3D.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index aad3e288a..69ffb8d22 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -68,8 +68,10 @@ static const float UNIT_MATRIX[] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f }; -static const float DEFAULT_BG_COLOR[3] = { 10.0f / 255.0f, 98.0f / 255.0f, 144.0f / 255.0f }; -static const float ERROR_BG_COLOR[3] = { 144.0f / 255.0f, 49.0f / 255.0f, 10.0f / 255.0f }; +static const float DEFAULT_BG_DARK_COLOR[3] = { 0.478f, 0.478f, 0.478f }; +static const float DEFAULT_BG_LIGHT_COLOR[3] = { 0.753f, 0.753f, 0.753f }; +static const float ERROR_BG_DARK_COLOR[3] = { 0.478f, 0.192f, 0.039f }; +static const float ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f }; namespace Slic3r { namespace GUI { @@ -5810,14 +5812,18 @@ void GLCanvas3D::_render_background() const ::glDisable(GL_DEPTH_TEST); ::glBegin(GL_QUADS); - ::glColor3f(0.0f, 0.0f, 0.0f); + if (m_dynamic_background_enabled && _is_any_volume_outside()) + ::glColor3fv(ERROR_BG_DARK_COLOR); + else + ::glColor3fv(DEFAULT_BG_DARK_COLOR); + ::glVertex2f(-1.0f, -1.0f); ::glVertex2f(1.0f, -1.0f); if (m_dynamic_background_enabled && _is_any_volume_outside()) - ::glColor3fv(ERROR_BG_COLOR); + ::glColor3fv(ERROR_BG_LIGHT_COLOR); else - ::glColor3fv(DEFAULT_BG_COLOR); + ::glColor3fv(DEFAULT_BG_LIGHT_COLOR); ::glVertex2f(1.0f, 1.0f); ::glVertex2f(-1.0f, 1.0f); From edb69289975740478467c2884a6fc9db582666d8 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Dec 2018 10:34:34 +0100 Subject: [PATCH 33/61] Fix to my last commit --- src/slic3r/GUI/GUI_ObjectList.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index e3e53a5e9..22ebc093f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -337,7 +337,7 @@ void ObjectList::selection_changed() void ObjectList::OnChar(wxKeyEvent& event) { - printf("KeyDown event\n"); +// printf("KeyDown event\n"); if (event.GetKeyCode() == WXK_BACK){ printf("WXK_BACK\n"); remove(); @@ -427,10 +427,10 @@ void ObjectList::key_event(wxKeyEvent& event) void ObjectList::OnBeginDrag(wxDataViewEvent &event) { - wxDataViewItem item(event.GetItem()); + const wxDataViewItem item(event.GetItem()); // only allow drags for item, not containers - if (multiple_selection() || + if (multiple_selection() || GetSelection()!=item || m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->GetItemType(item) != itVolume ) { event.Veto(); From 68de2d98137993ea5b00c7890a727339e2a73f18 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Dec 2018 11:36:02 +0100 Subject: [PATCH 34/61] Added cancellation points. Added new flip xy option. Refactor and waring removal. --- src/libslic3r/PrintConfig.cpp | 7 + src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/SLA/SLARotfinder.cpp | 1 - src/libslic3r/SLA/SLASupportTree.cpp | 183 ++++++++++++++---------- src/libslic3r/SLA/SLASupportTree.hpp | 2 +- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 6 +- src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 119 insertions(+), 84 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4b3b1e80a..419aa7206 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2388,6 +2388,13 @@ void PrintConfigDef::init_sla_params() def->min = 100; def->default_value = new ConfigOptionInt(1440); + def = this->add("display_flip_xy", coBool); + def->label = ("Flip X and Y axis"); + def->tooltip = L("Flip X and Y axis in the output raster"); + def->cli = "display-flip-xy=i"; + def->min = 0; + def->default_value = new ConfigOptionBool(true); + def = this->add("printer_correction", coFloats); def->full_label = L("Printer scaling correction"); def->tooltip = L("Printer scaling correction"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 9804d72ee..c3d17c451 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1035,6 +1035,7 @@ public: ConfigOptionFloat display_height; ConfigOptionInt display_pixels_x; ConfigOptionInt display_pixels_y; + ConfigOptionBool display_flip_xy; ConfigOptionFloats printer_correction; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) @@ -1046,6 +1047,7 @@ protected: OPT_PTR(display_height); OPT_PTR(display_pixels_x); OPT_PTR(display_pixels_y); + OPT_PTR(display_flip_xy); OPT_PTR(printer_correction); } }; diff --git a/src/libslic3r/SLA/SLARotfinder.cpp b/src/libslic3r/SLA/SLARotfinder.cpp index 05cea7748..e66e26706 100644 --- a/src/libslic3r/SLA/SLARotfinder.cpp +++ b/src/libslic3r/SLA/SLARotfinder.cpp @@ -20,7 +20,6 @@ std::array find_best_rotation(const ModelObject& modelobj, using libnest2d::opt::Optimizer; using libnest2d::opt::TOptimizer; using libnest2d::opt::StopCriteria; - using Quaternion = Eigen::Quaternion; static const unsigned MAX_TRIES = 100000; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 1914bed93..3dc9451e7 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -169,7 +169,7 @@ Contour3D cylinder(double r, double h, size_t ssteps) { auto steps = int(ssteps); auto& points = ret.points; auto& indices = ret.indices; - points.reserve(2*steps); + points.reserve(2*ssteps); double a = 2*PI/steps; Vec3d jp = {0, 0, 0}; @@ -189,7 +189,7 @@ Contour3D cylinder(double r, double h, size_t ssteps) { points.emplace_back(x, y, jp(Z)); } - indices.reserve(2*steps); + indices.reserve(2*ssteps); auto offs = steps; for(int i = 0; i < steps - 1; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); @@ -347,19 +347,22 @@ struct Pillar { double radius = 1, size_t st = 45): r(radius), steps(st), endpoint(endp), starts_from_head(false) { + assert(steps > 0); + int steps_1 = int(steps - 1); + auto& points = mesh.points; auto& indices = mesh.indices; points.reserve(2*steps); double a = 2*PI/steps; - for(int i = 0; i < steps; ++i) { + for(size_t i = 0; i < steps; ++i) { double phi = i*a; double x = jp(X) + r*std::cos(phi); double y = jp(Y) + r*std::sin(phi); points.emplace_back(x, y, jp(Z)); } - for(int i = 0; i < steps; ++i) { + for(size_t i = 0; i < steps; ++i) { double phi = i*a; double ex = endp(X) + r*std::cos(phi); double ey = endp(Y) + r*std::sin(phi); @@ -368,14 +371,13 @@ struct Pillar { indices.reserve(2*steps); int offs = int(steps); - for(int i = 0; i < steps - 1; ++i) { + for(int i = 0; i < steps_1 ; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); indices.emplace_back(i, offs + i + 1, i + 1); } - int last = int(steps) - 1; - indices.emplace_back(0, last, offs); - indices.emplace_back(last, offs + last, offs); + indices.emplace_back(0, steps_1, offs); + indices.emplace_back(steps_1, offs + steps_1, offs); } Pillar(const Junction& junc, const Vec3d& endp): @@ -390,19 +392,22 @@ struct Pillar { void add_base(double height = 3, double radius = 2) { if(height <= 0) return; + assert(steps > 0); + auto last = int(steps - 1); + if(radius < r ) radius = r; double a = 2*PI/steps; double z = endpoint(2) + height; - for(int i = 0; i < steps; ++i) { + for(size_t i = 0; i < steps; ++i) { double phi = i*a; double x = endpoint(0) + r*std::cos(phi); double y = endpoint(1) + r*std::sin(phi); base.points.emplace_back(x, y, z); } - for(int i = 0; i < steps; ++i) { + for(size_t i = 0; i < steps; ++i) { double phi = i*a; double x = endpoint(0) + radius*std::cos(phi); double y = endpoint(1) + radius*std::sin(phi); @@ -417,14 +422,13 @@ struct Pillar { auto hcenter = int(base.points.size() - 1); auto lcenter = int(base.points.size() - 2); auto offs = int(steps); - for(int i = 0; i < steps - 1; ++i) { + for(int i = 0; i < last; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); indices.emplace_back(i, offs + i + 1, i + 1); indices.emplace_back(i, i + 1, hcenter); indices.emplace_back(lcenter, offs + i + 1, offs + i); } - auto last = int(steps - 1); indices.emplace_back(0, last, offs); indices.emplace_back(last, offs + last, offs); indices.emplace_back(hcenter, last, 0); @@ -462,8 +466,6 @@ struct Bridge { Bridge(const Junction& j1, const Junction& j2, double r_mm = 0.8): Bridge(j1.pos, j2.pos, r_mm, j1.steps) {} - Bridge(const Junction& j, const Pillar& cl) {} - }; // A bridge that spans from model surface to model surface with small connecting @@ -537,12 +539,12 @@ EigenMesh3D to_eigenmesh(const Contour3D& cntr) { auto& V = emesh.V; auto& F = emesh.F; - V.resize(cntr.points.size(), 3); - F.resize(cntr.indices.size(), 3); + V.resize(Eigen::Index(cntr.points.size()), 3); + F.resize(Eigen::Index(cntr.indices.size()), 3); for (int i = 0; i < V.rows(); ++i) { - V.row(i) = cntr.points[i]; - F.row(i) = cntr.indices[i]; + V.row(i) = cntr.points[size_t(i)]; + F.row(i) = cntr.indices[size_t(i)]; } return emesh; @@ -569,18 +571,23 @@ EigenMesh3D to_eigenmesh(const TriangleMesh& tmesh) { V.resize(3*stl.stats.number_of_facets, 3); F.resize(stl.stats.number_of_facets, 3); - for (unsigned int i=0; ivertex[0](0); V(3*i+0, 1) = - facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2); - V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = - facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2); - V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = - facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2); + V(3*i+0, 0) = double(facet->vertex[0](0)); + V(3*i+0, 1) = double(facet->vertex[0](1)); + V(3*i+0, 2) = double(facet->vertex[0](2)); - F(i, 0) = 3*i+0; - F(i, 1) = 3*i+1; - F(i, 2) = 3*i+2; + V(3*i+1, 0) = double(facet->vertex[1](0)); + V(3*i+1, 1) = double(facet->vertex[1](1)); + V(3*i+1, 2) = double(facet->vertex[1](2)); + + V(3*i+2, 0) = double(facet->vertex[2](0)); + V(3*i+2, 1) = double(facet->vertex[2](1)); + V(3*i+2, 2) = double(facet->vertex[2](2)); + + F(i, 0) = int(3*i+0); + F(i, 1) = int(3*i+1); + F(i, 2) = int(3*i+2); } return outmesh; @@ -630,7 +637,7 @@ class SLASupportTree::Impl { Controller m_ctl; Pad m_pad; - mutable TriangleMesh meshcache; mutable bool meshcache_valid; + mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; mutable double model_height = 0; // the full height of the model public: double ground_level = 0; @@ -649,7 +656,7 @@ public: template Pillar& add_pillar(long headid, Args&&... args) { assert(headid >= 0 && headid < m_heads.size()); - Head& head = m_heads[headid]; + Head& head = m_heads[size_t(headid)]; m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); @@ -662,17 +669,17 @@ public: const Head& pillar_head(long pillar_id) const { assert(pillar_id >= 0 && pillar_id < m_pillars.size()); - const Pillar& p = m_pillars[pillar_id]; + const Pillar& p = m_pillars[size_t(pillar_id)]; assert(p.starts_from_head && p.start_junction_id >= 0 && p.start_junction_id < m_heads.size() ); - return m_heads[p.start_junction_id]; + return m_heads[size_t(p.start_junction_id)]; } const Pillar& head_pillar(long headid) const { assert(headid >= 0 && headid < m_heads.size()); - const Head& h = m_heads[headid]; + const Head& h = m_heads[size_t(headid)]; assert(h.pillar_id >= 0 && h.pillar_id < m_pillars.size()); - return m_pillars[h.pillar_id]; + return m_pillars[size_t(h.pillar_id)]; } template const Junction& add_junction(Args&&... args) { @@ -882,8 +889,8 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts, } // Find the leftmost (bottom) point - int l = 0; - for (int i = 1; i < n; i++) { + size_t l = 0; + for (size_t i = 1; i < n; i++) { if(std::abs(points[i](X) - points[l](X)) < ERR) { if(points[i](Y) < points[l](Y)) l = i; } @@ -894,7 +901,6 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts, // fill the output with the spatially ordered set of points. // find the direction - Vec2d dir = (points[l] - points[(l+1)%n]).normalized(); hull = inpts; auto& lp = points[l]; std::sort(hull.begin(), hull.end(), @@ -912,7 +918,7 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts, // Start from leftmost point, keep moving counterclockwise // until reach the start point again. This loop runs O(h) // times where h is number of points in result or output. - int p = l; + size_t p = l; do { // Add current point to result @@ -923,8 +929,8 @@ ClusterEl pts_convex_hull(const ClusterEl& inpts, // is to keep track of last visited most counterclock- // wise point in q. If any point 'i' is more counterclock- // wise than q, then update q. - int q = (p+1)%n; - for (int i = 0; i < n; i++) + size_t q = (p + 1) % n; + for (size_t i = 0; i < n; i++) { // If i is more counterclockwise than current q, then // update q @@ -1006,7 +1012,10 @@ bool SLASupportTree::generate(const PointSet &points, // std::cout << "p " << pn << " " << points.row(pn) << std::endl; // } - auto filterfn = [] ( + + auto& tifcl = ctl.cancelfn; + + auto filterfn = [tifcl] ( const SupportConfig& cfg, const PointSet& points, const EigenMesh3D& mesh, @@ -1016,26 +1025,29 @@ bool SLASupportTree::generate(const PointSet &points, PointSet& headless_pos, PointSet& headless_norm) { - /* ******************************************************** */ /* Filtering step */ /* ******************************************************** */ // Get the points that are too close to each other and keep only the // first one - auto aliases = cluster(points, - [cfg](const SpatElement& p, - const SpatElement& se){ + auto aliases = + cluster(points, + [tifcl](const SpatElement& p, const SpatElement& se) + { + tifcl(); return distance(p.first, se.first) < D_SP; }, 2); - filt_pts.resize(aliases.size(), 3); + filt_pts.resize(Eigen::Index(aliases.size()), 3); int count = 0; for(auto& a : aliases) { - // Here we keep only the front point of the cluster. TODO: centroid + // Here we keep only the front point of the cluster. filt_pts.row(count++) = points.row(a.front()); } + tifcl(); + // calculate the normals to the triangles belonging to filtered points auto nmls = sla::normals(filt_pts, mesh); @@ -1051,6 +1063,7 @@ bool SLASupportTree::generate(const PointSet &points, int pcount = 0, hlcount = 0; for(int i = 0; i < count; i++) { + tifcl(); auto n = nmls.row(i); // for all normals we generate the spherical coordinates and @@ -1110,7 +1123,7 @@ bool SLASupportTree::generate(const PointSet &points, }; // Function to write the pinheads into the result - auto pinheadfn = [] ( + auto pinheadfn = [tifcl] ( const SupportConfig& cfg, PointSet& head_pos, PointSet& nmls, @@ -1123,6 +1136,7 @@ bool SLASupportTree::generate(const PointSet &points, /* ******************************************************** */ for (int i = 0; i < head_pos.rows(); ++i) { + tifcl(); result.add_head( cfg.head_back_radius_mm, cfg.head_front_radius_mm, @@ -1136,7 +1150,7 @@ bool SLASupportTree::generate(const PointSet &points, // &filtered_points, &head_positions, &result, &mesh, // &gndidx, &gndheight, &nogndidx, cfg - auto classifyfn = [] ( + auto classifyfn = [tifcl] ( const SupportConfig& cfg, const EigenMesh3D& mesh, PointSet& head_pos, @@ -1152,11 +1166,12 @@ bool SLASupportTree::generate(const PointSet &points, /* ******************************************************** */ // We should first get the heads that reach the ground directly - gndheight.reserve(head_pos.rows()); - gndidx.reserve(head_pos.rows()); - nogndidx.reserve(head_pos.rows()); + gndheight.reserve(size_t(head_pos.rows())); + gndidx.reserve(size_t(head_pos.rows())); + nogndidx.reserve(size_t(head_pos.rows())); for(unsigned i = 0; i < head_pos.rows(); i++) { + tifcl(); auto& head = result.heads()[i]; Vec3d dir(0, 0, -1); @@ -1173,18 +1188,22 @@ bool SLASupportTree::generate(const PointSet &points, PointSet gnd(gndidx.size(), 3); for(size_t i = 0; i < gndidx.size(); i++) - gnd.row(i) = head_pos.row(gndidx[i]); + gnd.row(long(i)) = head_pos.row(gndidx[i]); // We want to search for clusters of points that are far enough from // each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, possibly in // their centroid support point. auto d_base = 2*cfg.base_radius_mm; - ground_clusters = cluster(gnd, - [d_base, &cfg](const SpatElement& p, const SpatElement& s){ - return distance(Vec2d(p.first(X), p.first(Y)), - Vec2d(s.first(X), s.first(Y))) < d_base; - }, 3); // max 3 heads to connect to one centroid + ground_clusters = + cluster( + gnd, + [d_base, tifcl](const SpatElement& p, const SpatElement& s) + { + tifcl(); + return distance(Vec2d(p.first(X), p.first(Y)), + Vec2d(s.first(X), s.first(Y))) < d_base; + }, 3); // max 3 heads to connect to one centroid }; // Helper function for interconnecting two pillars with zig-zag bridges @@ -1197,9 +1216,6 @@ bool SLASupportTree::generate(const PointSet &points, const Head& phead = result.pillar_head(pillar.id); const Head& nextphead = result.pillar_head(nextpillar.id); -// double d = 2*pillar.r; -// const Vec3d& pp = pillar.endpoint.cwiseProduct(Vec3d{1, 1, 0}); - Vec3d sj = phead.junction_point(); sj(Z) = std::min(sj(Z), nextphead.junction_point()(Z)); Vec3d ej = nextpillar.endpoint; @@ -1244,7 +1260,7 @@ bool SLASupportTree::generate(const PointSet &points, } }; - auto routing_ground_fn = [gnd_head_pt, interconnect]( + auto routing_ground_fn = [gnd_head_pt, interconnect, tifcl]( const SupportConfig& cfg, const ClusteredPoints& gnd_clusters, const IndexSet& gndidx, @@ -1260,22 +1276,27 @@ bool SLASupportTree::generate(const PointSet &points, cl_centroids.reserve(gnd_clusters.size()); SpatIndex pheadindex; // spatial index for the junctions - for(auto cl : gnd_clusters) { + for(auto& cl : gnd_clusters) { tifcl(); // place all the centroid head positions into the index. We will // query for alternative pillar positions. If a sidehead cannot // connect to the cluster centroid, we have to search for another // head with a full pillar. Also when there are two elements in the // cluster, the centroid is arbitrary and the sidehead is allowed to // connect to a nearby pillar to increase structural stability. + if(cl.empty()) continue; // get the current cluster centroid - unsigned cid = cluster_centroid(cl, gnd_head_pt, - [](const Vec3d& p1, const Vec3d& p2) + long lcid = cluster_centroid(cl, gnd_head_pt, + [tifcl](const Vec3d& p1, const Vec3d& p2) { + tifcl(); return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y))); }); - cl_centroids.push_back(cid); + assert(lcid > 0); + auto cid = unsigned(lcid); + + cl_centroids.push_back(unsigned(cid)); unsigned hid = gndidx[cl[cid]]; // Head index Head& h = result.head(hid); @@ -1288,12 +1309,13 @@ bool SLASupportTree::generate(const PointSet &points, // now we will go through the clusters ones again and connect the // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. - long ci = 0; - for(auto cl : gnd_clusters) { + size_t ci = 0; + for(auto cl : gnd_clusters) { tifcl(); + auto cidx = cl_centroids[ci]; cl_centroids[ci++] = cl[cidx]; - long index_to_heads = gndidx[cl[cidx]]; + size_t index_to_heads = gndidx[cl[cidx]]; auto& head = result.head(index_to_heads); Vec3d startpoint = head.junction_point(); @@ -1301,7 +1323,7 @@ bool SLASupportTree::generate(const PointSet &points, // Create the central pillar of the cluster with its base on the // ground - result.add_pillar(index_to_heads, endpoint, pradius) + result.add_pillar(long(index_to_heads), endpoint, pradius) .add_base(cfg.base_height_mm, cfg.base_radius_mm); // Process side point in current cluster @@ -1352,12 +1374,11 @@ bool SLASupportTree::generate(const PointSet &points, return nearest_id; }; - for(auto c : cl) { + for(auto c : cl) { tifcl(); auto& sidehead = result.head(gndidx[c]); sidehead.transform(); Vec3d jsh = sidehead.junction_point(); -// Vec3d jp2d = {jsh(X), jsh(Y), gndlvl}; SpatIndex spindex = pheadindex; long nearest_id = search_nearest(spindex, jsh); @@ -1374,7 +1395,7 @@ bool SLASupportTree::generate(const PointSet &points, } else { // Creating the bridge to the nearest pillar - const Head& nearhead = result.heads()[nearest_id]; + const Head& nearhead = result.heads()[size_t(nearest_id)]; Vec3d jp = jsh; Vec3d jh = nearhead.junction_point(); @@ -1419,6 +1440,7 @@ bool SLASupportTree::generate(const PointSet &points, ClusterEl ring; while(!rem.empty()) { // loop until all the points belong to some ring + tifcl(); std::sort(rem.begin(), rem.end()); auto newring = pts_convex_hull(rem, @@ -1432,7 +1454,8 @@ bool SLASupportTree::generate(const PointSet &points, SpatIndex innerring; for(unsigned i : newring) { const Pillar& pill = result.head_pillar(gndidx[i]); - innerring.insert(pill.endpoint, pill.id); + assert(pill.id > 0); + innerring.insert(pill.endpoint, unsigned(pill.id)); } // For all pillars in the outer ring find the closest in the @@ -1478,14 +1501,14 @@ bool SLASupportTree::generate(const PointSet &points, } }; - auto routing_nongnd_fn = []( + auto routing_nongnd_fn = [tifcl]( const SupportConfig& cfg, const std::vector& gndheight, const IndexSet& nogndidx, Result& result) { // TODO: connect these to the ground pillars if possible - for(auto idx : nogndidx) { + for(auto idx : nogndidx) { tifcl(); auto& head = result.head(idx); head.transform(); @@ -1510,7 +1533,7 @@ bool SLASupportTree::generate(const PointSet &points, } }; - auto process_headless = []( + auto process_headless = [tifcl]( const SupportConfig& cfg, const PointSet& headless_pts, const PointSet& headless_norm, @@ -1525,7 +1548,7 @@ bool SLASupportTree::generate(const PointSet &points, // We will sink the pins into the model surface for a distance of 1/3 of // HWIDTH_MM - for(int i = 0; i < headless_pts.rows(); i++) { + for(int i = 0; i < headless_pts.rows(); i++) { tifcl(); Vec3d sp = headless_pts.row(i); Vec3d n = headless_norm.row(i); @@ -1634,6 +1657,7 @@ bool SLASupportTree::generate(const PointSet &points, case HALT: pc = pc_prev; break; case DONE: case ABORT: break; + default: ; } ctl.statuscb(stepstate[pc], stepstr[pc]); }; @@ -1724,5 +1748,8 @@ SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c) SLASupportTree::~SLASupportTree() {} +SLASupportsStoppedException::SLASupportsStoppedException(): + std::runtime_error("") {} + } } diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 90162d6b5..62e790611 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -114,7 +114,7 @@ PointSet to_point_set(const std::vector&); class SLASupportsStoppedException: public std::runtime_error { public: using std::runtime_error::runtime_error; - SLASupportsStoppedException(): std::runtime_error("") {} + SLASupportsStoppedException(); }; /// The class containing mesh data for the generated supports. diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index db50a18cf..50d7775a2 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -89,8 +89,6 @@ PointSet normals(const PointSet& points, const EigenMesh3D& mesh) { #ifdef IGL_COMPATIBLE Eigen::VectorXd dists; Eigen::VectorXi I; -// Eigen::Matrix dists; -// Eigen::Matrix I; PointSet C; igl::point_mesh_squared_distance( points, mesh.V, mesh.F, dists, I, C); @@ -122,7 +120,7 @@ double ray_mesh_intersect(const Vec3d& s, igl::Hit hit; hit.t = std::numeric_limits::infinity(); igl::ray_mesh_intersect(s, dir, m.V, m.F, hit); - return hit.t; + return double(hit.t); } // Clustering a set of points by the given criteria @@ -208,7 +206,7 @@ Segments model_boundary(const EigenMesh3D& emesh, double offs) { Segments ret; Polygons pp; - pp.reserve(emesh.F.rows()); + pp.reserve(size_t(emesh.F.rows())); for (int i = 0; i < emesh.F.rows(); i++) { auto trindex = emesh.F.row(i); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 3100f25a1..182181e70 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -446,6 +446,7 @@ const std::vector& Preset::sla_printer_options() "printer_technology", "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", + "display_flip_xy", "printer_correction", "printer_notes", "inherits" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index fa39ac240..66fd0e2e8 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1883,6 +1883,7 @@ void TabPrinter::build_sla() line.append_option(option); line.append_option(optgroup->get_option("display_pixels_y")); optgroup->append_line(line); + optgroup->append_single_option_line("display_flip_xy"); optgroup = page->new_optgroup(_(L("Corrections"))); line = Line{ m_config->def()->get("printer_correction")->full_label, "" }; From 706a9d6ddff5ccfaf73be44aa883bd641b49a6c6 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 12 Dec 2018 11:44:17 +0100 Subject: [PATCH 35/61] Custom bed color --- src/slic3r/GUI/GLCanvas3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 69ffb8d22..cc86b9b7a 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -581,7 +581,7 @@ void GLCanvas3D::Bed::_render_custom() const ::glEnableClientState(GL_VERTEX_ARRAY); - ::glColor4f(0.8f, 0.6f, 0.5f, 0.4f); + ::glColor4f(0.35f, 0.35f, 0.35f, 0.4f); ::glNormal3d(0.0f, 0.0f, 1.0f); ::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()); ::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount); From c0ebcacf1d62d44d247755c9c0d9c681d3579651 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 12:00:45 +0100 Subject: [PATCH 36/61] WIP: Time estimate in file names. --- src/libslic3r/Print.cpp | 13 ++++++++ src/libslic3r/Print.hpp | 3 +- src/libslic3r/PrintBase.cpp | 10 +++--- src/libslic3r/PrintBase.hpp | 2 +- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 34 +++++++++++++++++++-- src/slic3r/GUI/GUI.cpp | 2 +- 6 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e382c7dbc..3bbe00805 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1860,5 +1860,18 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion std::max(region.config().perimeter_extruder.value - 1, 0); } +std::string Print::output_filename() const +{ + // Set the placeholders for the data know first after the G-code export is finished. + // These values will be just propagated into the output file name. + DynamicConfig config; + for (const std::string &key : { + "print_time", "normal_print_time", "silent_print_time", + "used_filament", "extruded_volume", "total_cost", "total_weight", + "total_wipe_tower_cost", "total_wipe_tower_filament"}) + config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); + return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config); +} + } // namespace Slic3r diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 04de0f95c..27ee6f3c2 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -345,8 +345,7 @@ public: bool has_wipe_tower() const; const WipeTowerData& wipe_tower_data() const { return m_wipe_tower_data; } - std::string output_filename() const override - { return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode"); } + std::string output_filename() const override; // Accessed by SupportMaterial const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; } diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index cf8c5b193..1d078da30 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -48,12 +48,14 @@ void PrintBase::update_object_placeholders() } } -std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext) const +std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override) const { - DynamicConfig cfg_timestamp; - PlaceholderParser::update_timestamp(cfg_timestamp); + DynamicConfig cfg; + if (config_override != nullptr) + cfg = *config_override; + PlaceholderParser::update_timestamp(cfg); try { - boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg_timestamp); + boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg); if (filename.extension().empty()) filename = boost::filesystem::change_extension(filename, default_ext); return filename.string(); diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 401718773..1a61921d6 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -307,7 +307,7 @@ protected: void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); } // To be called by this->output_filename() with the format string pulled from the configuration layer. - std::string output_filename(const std::string &format, const std::string &default_ext) const; + std::string output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override = nullptr) const; // Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects. void update_object_placeholders(); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 1e8258d6f..f2249de3d 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -22,6 +22,7 @@ #include #include +#include #include namespace Slic3r { @@ -72,11 +73,38 @@ void BackgroundSlicingProcess::process_fff() if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { //FIXME localize the messages - if (copy_file(m_temp_output_path, m_export_path) != 0) + // Perform the final post-processing of the export path by applying the print statistics over the file name. + std::string export_path; + { + const PrintStatistics &stats = m_fff_print->print_statistics(); + PlaceholderParser pp; + std::string normal_print_time = stats.estimated_normal_print_time; + std::string silent_print_time = stats.estimated_silent_print_time; + normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), std::isspace), normal_print_time.end()); + silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), std::isspace), silent_print_time.end()); + pp.set("print_time", new ConfigOptionString(normal_print_time)); + pp.set("normal_print_time", new ConfigOptionString(normal_print_time)); + pp.set("silent_print_time", new ConfigOptionString(silent_print_time)); + pp.set("used_filament", new ConfigOptionFloat (stats.total_used_filament)); + pp.set("extruded_volume", new ConfigOptionFloat (stats.total_extruded_volume)); + pp.set("total_cost", new ConfigOptionFloat (stats.total_cost)); + pp.set("total_weight", new ConfigOptionFloat (stats.total_weight)); + pp.set("total_wipe_tower_cost", new ConfigOptionFloat (stats.total_wipe_tower_cost)); + pp.set("total_wipe_tower_filament", new ConfigOptionFloat (stats.total_wipe_tower_filament)); + boost::filesystem::path path(m_export_path); + try { + std::string new_stem = pp.process(path.stem().string(), 0); + export_path = (path.parent_path() / (new_stem + path.extension().string())).string(); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what(); + export_path = m_export_path; + } + } + if (copy_file(m_temp_output_path, export_path) != 0) throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); m_print->set_status(95, "Running post-processing scripts"); - run_post_process_scripts(m_export_path, m_fff_print->config()); - m_print->set_status(100, "G-code file exported to " + m_export_path); + run_post_process_scripts(export_path, m_fff_print->config()); + m_print->set_status(100, "G-code file exported to " + export_path); } else { m_print->set_status(100, "Slicing complete"); } diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 2423c152f..bc7ea9899 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -147,7 +147,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast(value))); break; case coStrings:{ - if (opt_key == "compatible_prints" || opt_key == "compatible_printers") { + if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "post_process") { config.option(opt_key)->values = boost::any_cast>(value); } From 0bba11645533e4db2e7b839092a3c36e9b6f85d7 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Wed, 12 Dec 2018 13:04:06 +0100 Subject: [PATCH 37/61] Fixed rendering of gizmo move for wipe tower --- src/slic3r/GUI/GLGizmo.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 119838dad..7f62d0901 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -1292,9 +1292,11 @@ void GLGizmoMove3D::on_render(const GLCanvas3D::Selection& selection) const // draw grabbers render_grabbers(box); - render_grabber_extension(X, box, false); - render_grabber_extension(Y, box, false); - render_grabber_extension(Z, box, false); + for (unsigned int i = 0; i < 3; ++i) + { + if (m_grabbers[i].enabled) + render_grabber_extension((Axis)i, box, false); + } } else { From 2350fb62b9db77d67b5722751ad8013e38573464 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Tue, 11 Dec 2018 10:33:11 +0100 Subject: [PATCH 38/61] WIP OctoPrint integration --- src/libslic3r/PrintConfig.cpp | 3 +- src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 28 +++- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 7 + src/slic3r/GUI/GUI_App.cpp | 2 + src/slic3r/GUI/GUI_App.hpp | 5 + src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 154 ++++++++++-------- src/slic3r/GUI/Plater.hpp | 14 +- src/slic3r/GUI/PrintHostDialogs.cpp | 49 ++++++ .../PrintHostDialogs.hpp} | 27 ++- src/slic3r/Utils/Duet.cpp | 11 +- src/slic3r/Utils/Duet.hpp | 1 + src/slic3r/Utils/OctoPrint.cpp | 15 +- src/slic3r/Utils/OctoPrint.hpp | 1 + src/slic3r/Utils/PrintHost.cpp | 51 +++++- src/slic3r/Utils/PrintHost.hpp | 60 +++++-- src/slic3r/Utils/PrintHostSendDialog.cpp | 52 ------ 18 files changed, 314 insertions(+), 172 deletions(-) create mode 100644 src/slic3r/GUI/PrintHostDialogs.cpp rename src/slic3r/{Utils/PrintHostSendDialog.hpp => GUI/PrintHostDialogs.hpp} (60%) delete mode 100644 src/slic3r/Utils/PrintHostSendDialog.cpp diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4b3b1e80a..086d456de 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1297,10 +1297,11 @@ void PrintConfigDef::init_fff_params() def->default_value = new ConfigOptionString(""); def = this->add("printhost_cafile", coString); - def->label = "HTTPS CA file"; + def->label = "HTTPS CA File"; def->tooltip = "Custom CA certificate file can be specified for HTTPS OctoPrint connections, in crt/pem format. " "If left blank, the default OS CA certificate repository is used."; def->cli = "printhost-cafile=s"; + def->mode = comAdvanced; def->default_value = new ConfigOptionString(""); def = this->add("print_host", coString); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index e8031309c..3b6bad16d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -103,12 +103,12 @@ add_library(libslic3r_gui STATIC GUI/ProgressIndicator.hpp GUI/ProgressStatusBar.hpp GUI/ProgressStatusBar.cpp + GUI/PrintHostDialogs.cpp + GUI/PrintHostDialogs.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp Utils/FixModelByWin10.hpp - Utils/PrintHostSendDialog.cpp - Utils/PrintHostSendDialog.hpp Utils/OctoPrint.cpp Utils/OctoPrint.hpp Utils/Duet.cpp diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index f2249de3d..1829de959 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -19,9 +19,12 @@ //#undef NDEBUG #include #include +#include +#include #include #include +#include #include #include @@ -62,6 +65,11 @@ PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const return m_print->technology(); } +static bool isspace(int ch) +{ + return std::isspace(ch) != 0; +} + // This function may one day be merged into the Print, but historically the print was separated // from the G-code generator. void BackgroundSlicingProcess::process_fff() @@ -80,8 +88,8 @@ void BackgroundSlicingProcess::process_fff() PlaceholderParser pp; std::string normal_print_time = stats.estimated_normal_print_time; std::string silent_print_time = stats.estimated_silent_print_time; - normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), std::isspace), normal_print_time.end()); - silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), std::isspace), silent_print_time.end()); + normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), isspace), normal_print_time.end()); + silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), isspace), silent_print_time.end()); pp.set("print_time", new ConfigOptionString(normal_print_time)); pp.set("normal_print_time", new ConfigOptionString(normal_print_time)); pp.set("silent_print_time", new ConfigOptionString(silent_print_time)); @@ -373,6 +381,22 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path) m_export_path = path; } +void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job) +{ + assert(m_export_path.empty()); + if (! m_export_path.empty()) + return; + + const auto path = boost::filesystem::temp_directory_path() + / boost::filesystem::unique_path(".upload.%%%%-%%%%-%%%%-%%%%.gcode"); + + // Guard against entering the export step before changing the export path. + tbb::mutex::scoped_lock lock(m_print->state_mutex()); + this->invalidate_step(bspsGCodeFinalize); + m_export_path = path.native(); + m_upload_job = std::move(upload_job); +} + void BackgroundSlicingProcess::reset_export() { assert(! this->running()); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index bb072fb98..222ed147e 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -9,6 +9,7 @@ #include #include "libslic3r/Print.hpp" +#include "slic3r/Utils/PrintHost.hpp" namespace Slic3r { @@ -86,6 +87,9 @@ public: // Set the export path of the G-code. // Once the path is set, the G-code void schedule_export(const std::string &path); + // Set print host upload job data to be enqueued to the PrintHostJobQueue + // after current print slicing is complete + void schedule_upload(Slic3r::PrintHostJob upload_job); // Clear m_export_path. void reset_export(); // Once the G-code export is scheduled, the apply() methods will do nothing. @@ -143,6 +147,9 @@ private: // Output path provided by the user. The output path may be set even if the slicing is running, // but once set, it cannot be re-set. std::string m_export_path; + // Print host upload job to schedule after slicing is complete, used by schedule_upload(), + // empty by default (ie. no upload to schedule) + PrintHostJob m_upload_job; // Thread, on which the background processing is executed. The thread will always be present // and ready to execute the slicing process. std::thread m_thread; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 73d6a03b0..f4047ae3e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -27,6 +27,7 @@ #include "3DScene.hpp" #include "../Utils/PresetUpdater.hpp" +#include "../Utils/PrintHost.hpp" #include "ConfigWizard_private.hpp" #include "slic3r/Config/Snapshot.hpp" #include "ConfigSnapshotDialog.hpp" @@ -72,6 +73,7 @@ GUI_App::GUI_App() : wxApp() #if ENABLE_IMGUI , m_imgui(new ImGuiWrapper()) + , m_printhost_queue(new PrintHostJobQueue()) #endif // ENABLE_IMGUI {} diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 9df90259a..875a92456 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -27,6 +27,7 @@ class AppConfig; class PresetBundle; class PresetUpdater; class ModelObject; +class PrintHostJobQueue; namespace GUI { @@ -91,6 +92,8 @@ class GUI_App : public wxApp std::unique_ptr m_imgui; #endif // ENABLE_IMGUI + std::unique_ptr m_printhost_queue; + public: bool OnInit() override; @@ -161,6 +164,8 @@ public: ImGuiWrapper* imgui() { return m_imgui.get(); } #endif // ENABLE_IMGUI + PrintHostJobQueue& printhost_queue() { return *m_printhost_queue.get(); } + }; DECLARE_APP(GUI_App) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b3dc8ba75..a74d5f72a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -794,7 +794,7 @@ void MainFrame::update_ui_from_settings() { bool bp_on = wxGetApp().app_config->get("background_processing") == "1"; m_menu_item_reslice_now->Enable(bp_on); - m_plater->sidebar().show_button(baReslice, !bp_on); + m_plater->sidebar().show_reslice(!bp_on); m_plater->sidebar().Layout(); if (m_plater) m_plater->update_ui_from_settings(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index af8769814..5ce49259a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -54,7 +54,9 @@ #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" +#include "PrintHostDialogs.hpp" #include "../Utils/ASCIIFolding.hpp" +#include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include // Needs to be last because reasons :-/ @@ -64,6 +66,7 @@ using boost::optional; namespace fs = boost::filesystem; using Slic3r::_3DScene; using Slic3r::Preset; +using Slic3r::PrintHostJob; namespace Slic3r { @@ -447,7 +450,6 @@ struct Sidebar::priv wxButton *btn_export_gcode; wxButton *btn_reslice; - // wxButton *btn_print; // XXX: remove wxButton *btn_send_gcode; priv(Plater *plater) : plater(plater) {} @@ -543,13 +545,12 @@ Sidebar::Sidebar(Plater *parent) p->object_settings->Hide(); p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20); - // Buttons in the scrolled area wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); p->btn_send_gcode = new wxButton(p->scrolled, wxID_ANY, _(L("Send to printer"))); + p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); p->btn_send_gcode->SetBitmap(arrow_up); + p->btn_send_gcode->SetFont(wxGetApp().bold_font()); p->btn_send_gcode->Hide(); - auto *btns_sizer_scrolled = new wxBoxSizer(wxHORIZONTAL); - btns_sizer_scrolled->Add(p->btn_send_gcode); // Info boxes p->object_info = new ObjectInfo(p->scrolled); @@ -559,7 +560,6 @@ Sidebar::Sidebar(Plater *parent) scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, 2); scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND); scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, 20); - scrolled_sizer->Add(btns_sizer_scrolled, 0, wxEXPAND, 0); scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, 20); // Buttons underneath the scrolled area @@ -571,6 +571,7 @@ Sidebar::Sidebar(Plater *parent) auto *btns_sizer = new wxBoxSizer(wxVERTICAL); btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, 5); + btns_sizer->Add(p->btn_send_gcode, 0, wxEXPAND | wxTOP, 5); btns_sizer->Add(p->btn_export_gcode, 0, wxEXPAND | wxTOP, 5); auto *sizer = new wxBoxSizer(wxVERTICAL); @@ -820,14 +821,6 @@ void Sidebar::show_sliced_info_sizer(const bool show) p->scrolled->Refresh(); } -void Sidebar::show_buttons(const bool show) -{ - p->btn_reslice->Show(show); - TabPrinter *tab = dynamic_cast(wxGetApp().get_tab(Preset::TYPE_PRINTER)); - if (tab && p->plater->printer_technology() == ptFFF) - p->btn_send_gcode->Show(show && !tab->m_config->opt_string("print_host").empty()); -} - void Sidebar::enable_buttons(bool enable) { p->btn_reslice->Enable(enable); @@ -835,23 +828,8 @@ void Sidebar::enable_buttons(bool enable) p->btn_send_gcode->Enable(enable); } -void Sidebar::show_button(ButtonAction but_action, bool show) -{ - switch (but_action) - { - case baReslice: - p->btn_reslice->Show(show); - break; - case baExportGcode: - p->btn_export_gcode->Show(show); - break; - case baSendGcode: - p->btn_send_gcode->Show(show); - break; - default: - break; - } -} +void Sidebar::show_reslice(bool show) { p->btn_reslice->Show(show); } +void Sidebar::show_send(bool show) { p->btn_send_gcode->Show(show); } bool Sidebar::is_multifilament() { @@ -1008,6 +986,7 @@ struct Plater::priv }; // returns bit mask of UpdateBackgroundProcessReturnState unsigned int update_background_process(); + void export_gcode(fs::path output_path, PrintHostJob upload_job); void async_apply_config(); void reload_from_disk(); void fix_through_netfabb(const int obj_idx); @@ -2059,6 +2038,45 @@ unsigned int Plater::priv::update_background_process() return return_state; } +void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job) +{ + wxCHECK_RET(!(output_path.empty() && upload_job.empty()), "export_gcode: output_path and upload_job empty"); + + if (model.objects.empty()) + return; + + if (background_process.is_export_scheduled()) { + GUI::show_error(q, _(L("Another export job is currently running."))); + return; + } + + // bitmask of UpdateBackgroundProcessReturnState + unsigned int state = update_background_process(); + if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) +#if ENABLE_REMOVE_TABS_FROM_PLATER + view3D->reload_scene(false); +#else + canvas3D->reload_scene(false); +#endif // ENABLE_REMOVE_TABS_FROM_PLATER + if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) + return; + + if (! output_path.empty()) { + background_process.schedule_export(output_path.string()); + } else { + background_process.schedule_upload(std::move(upload_job)); + } + + if (! background_process.running()) { + // The print is valid and it should be started. + if (background_process.start()) + statusbar()->set_cancel_callback([this]() { + statusbar()->set_status_text(L("Cancelling")); + background_process.stop(); + }); + } +} + void Plater::priv::async_apply_config() { // bitmask of UpdateBackgroundProcessReturnState @@ -2912,42 +2930,26 @@ void Plater::export_gcode(fs::path output_path) if (p->model.objects.empty()) return; - if (this->p->background_process.is_export_scheduled()) { - GUI::show_error(this, _(L("Another export job is currently running."))); - return; - } - - // bitmask of UpdateBackgroundProcessReturnState - unsigned int state = this->p->update_background_process(); - if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) -#if ENABLE_REMOVE_TABS_FROM_PLATER - this->p->view3D->reload_scene(false); -#else - this->p->canvas3D->reload_scene(false); -#endif // ENABLE_REMOVE_TABS_FROM_PLATER - if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0) - return; - // select output file if (output_path.empty()) { // XXX: take output path from CLI opts? Ancient Slic3r versions used to do that... // If possible, remove accents from accented latin characters. // This function is useful for generating file names to be processed by legacy firmwares. - fs::path default_output_file; + fs::path default_output_file; try { - default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string()); + default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string()); } catch (const std::exception &ex) { show_error(this, ex.what()); return; } - default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); + default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string()); - wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")), + wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")), start_dir, default_output_file.filename().string(), - GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), + GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()), wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); @@ -2958,23 +2960,15 @@ void Plater::export_gcode(fs::path output_path) } } else { try { - output_path = this->p->background_process.current_print()->output_filepath(output_path.string()); + output_path = this->p->background_process.current_print()->output_filepath(output_path.string()); } catch (const std::exception &ex) { show_error(this, ex.what()); return; } } - if (! output_path.empty()) - this->p->background_process.schedule_export(output_path.string()); - - if ((! output_path.empty() || this->p->background_processing_enabled()) && ! this->p->background_process.running()) { - // The print is valid and it should be started. - if (this->p->background_process.start()) - this->p->statusbar()->set_cancel_callback([this]() { - this->p->statusbar()->set_status_text(L("Cancelling")); - this->p->background_process.stop(); - }); + if (! output_path.empty()) { + p->export_gcode(std::move(output_path), PrintHostJob()); } } @@ -3077,7 +3071,28 @@ void Plater::reslice() void Plater::send_gcode() { -// p->send_gcode_file = export_gcode(); + if (p->model.objects.empty()) { return; } + + PrintHostJob upload_job(p->config); + if (upload_job.empty()) { return; } + + // Obtain default output path + fs::path default_output_file; + try { + default_output_file = this->p->background_process.current_print()->output_filepath(""); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + return; + } + default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string())); + + Slic3r::PrintHostSendDialog dlg(default_output_file); + if (dlg.ShowModal() == wxID_OK) { + upload_job.upload_data.upload_path = dlg.filename(); + upload_job.upload_data.start_print = dlg.start_print(); + + p->export_gcode(fs::path(), std::move(upload_job)); + } } void Plater::on_extruders_change(int num_extruders) @@ -3127,14 +3142,6 @@ void Plater::on_config_change(const DynamicPrintConfig &config) opt_key == "single_extruder_multi_material") { update_scheduled = true; } -// else if(opt_key == "serial_port") { -// sidebar()->p->btn_print->Show(config.get("serial_port")); // ???: btn_print is removed -// Layout(); -// } - else if (opt_key == "print_host") { - sidebar().show_button(baReslice, !p->config->option(opt_key)->value.empty()); - Layout(); - } else if(opt_key == "variable_layer_height") { if (p->config->opt_bool("variable_layer_height") != true) { #if ENABLE_REMOVE_TABS_FROM_PLATER @@ -3174,6 +3181,11 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } } + { + const auto prin_host_opt = p->config->option("print_host"); + p->sidebar->show_send(prin_host_opt != nullptr && !prin_host_opt->value.empty()); + } + if (update_scheduled) update(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 96755ab10..334ab740a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -55,14 +55,6 @@ private: int extruder_idx = -1; }; -enum ButtonAction -{ - baUndef, - baReslice, - baExportGcode, - baSendGcode -}; - class Sidebar : public wxPanel { /*ConfigMenuIDs*/int m_mode; @@ -88,9 +80,9 @@ public: void update_objects_list_extruder_column(int extruders_count); void show_info_sizer(); void show_sliced_info_sizer(const bool show); - void show_buttons(const bool show); - void show_button(ButtonAction but_action, bool show); void enable_buttons(bool enable); + void show_reslice(bool show); + void show_send(bool show); bool is_multifilament(); void set_mode_value(const /*ConfigMenuIDs*/int mode) { m_mode = mode; } @@ -103,6 +95,8 @@ private: class Plater: public wxPanel { public: + using fs_path = boost::filesystem::path; + Plater(wxWindow *parent, MainFrame *main_frame); Plater(Plater &&) = delete; Plater(const Plater &) = delete; diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp new file mode 100644 index 000000000..a5de7c3c6 --- /dev/null +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -0,0 +1,49 @@ +#include "PrintHostDialogs.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/I18N.hpp" + +namespace fs = boost::filesystem; + +namespace Slic3r { + +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path) + : MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE) + , txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())) + , box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))) +{ + auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); + label_dir_hint->Wrap(CONTENT_WIDTH); + + content_sizer->Add(txt_filename, 0, wxEXPAND); + content_sizer->Add(label_dir_hint); + content_sizer->AddSpacer(VERT_SPACING); + content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); + + btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); + + txt_filename->SetFocus(); + wxString stem(path.stem().wstring()); + txt_filename->SetSelection(0, stem.Length()); + + Fit(); +} + +fs::path PrintHostSendDialog::filename() const +{ + return fs::path(txt_filename->GetValue().wx_str()); +} + +bool PrintHostSendDialog::start_print() const +{ + return box_print->GetValue(); } +} diff --git a/src/slic3r/Utils/PrintHostSendDialog.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp similarity index 60% rename from src/slic3r/Utils/PrintHostSendDialog.hpp rename to src/slic3r/GUI/PrintHostDialogs.hpp index dc4a8d6f7..d27fbe576 100644 --- a/src/slic3r/Utils/PrintHostSendDialog.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -20,19 +20,30 @@ namespace Slic3r { + class PrintHostSendDialog : public GUI::MsgDialog { -private: - wxTextCtrl *txt_filename; - wxCheckBox *box_print; - bool can_start_print; - public: - PrintHostSendDialog(const boost::filesystem::path &path, bool can_start_print); - boost::filesystem::path filename() const; - bool print() const; + PrintHostSendDialog(const boost::filesystem::path &path); + boost::filesystem::path filename() const; + bool start_print() const; + +private: + wxTextCtrl *txt_filename; + wxCheckBox *box_print; + bool can_start_print; }; + +class PrintHostQueueDialog : public wxDialog +{ +public: + PrintHostQueueDialog(); + +private: +}; + + } #endif diff --git a/src/slic3r/Utils/Duet.cpp b/src/slic3r/Utils/Duet.cpp index c242e918e..4eda7bd46 100644 --- a/src/slic3r/Utils/Duet.cpp +++ b/src/slic3r/Utils/Duet.cpp @@ -1,5 +1,4 @@ #include "Duet.hpp" -#include "PrintHostSendDialog.hpp" #include #include @@ -21,6 +20,7 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/MsgDialog.hpp" +#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX #include "Http.hpp" namespace fs = boost::filesystem; @@ -62,10 +62,10 @@ bool Duet::send_gcode(const std::string &filename) const const auto errortitle = _(L("Error while uploading to the Duet")); fs::path filepath(filename); - PrintHostSendDialog send_dialog(filepath.filename(), true); + PrintHostSendDialog send_dialog(filepath.filename()); if (send_dialog.ShowModal() != wxID_OK) { return false; } - const bool print = send_dialog.print(); + const bool print = send_dialog.start_print(); const auto upload_filepath = send_dialog.filename(); const auto upload_filename = upload_filepath.filename(); const auto upload_parent_path = upload_filepath.parent_path(); @@ -136,6 +136,11 @@ bool Duet::send_gcode(const std::string &filename) const return res; } +bool Duet::upload(PrintHostUpload upload_data) const +{ + throw "unimplemented"; +} + bool Duet::has_auto_discovery() const { return false; diff --git a/src/slic3r/Utils/Duet.hpp b/src/slic3r/Utils/Duet.hpp index bc210d7a4..db21fd0a1 100644 --- a/src/slic3r/Utils/Duet.hpp +++ b/src/slic3r/Utils/Duet.hpp @@ -24,6 +24,7 @@ public: wxString get_test_failed_msg (wxString &msg) const; // Send gcode file to duet, filename is expected to be in UTF-8 bool send_gcode(const std::string &filename) const; + bool upload(PrintHostUpload upload_data) const; bool has_auto_discovery() const; bool can_test() const; private: diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index d975578fd..b2e2d4d45 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -1,14 +1,14 @@ #include "OctoPrint.hpp" -#include "PrintHostSendDialog.hpp" #include #include #include #include "libslic3r/PrintConfig.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/PrintHostDialogs.hpp" // XXX #include "Http.hpp" -#include "slic3r/GUI/I18N.hpp" namespace fs = boost::filesystem; @@ -66,10 +66,10 @@ bool OctoPrint::send_gcode(const std::string &filename) const const auto errortitle = _(L("Error while uploading to the OctoPrint server")); fs::path filepath(filename); - PrintHostSendDialog send_dialog(filepath.filename(), true); + PrintHostSendDialog send_dialog(filepath.filename()); if (send_dialog.ShowModal() != wxID_OK) { return false; } - const bool print = send_dialog.print(); + const bool print = send_dialog.start_print(); const auto upload_filepath = send_dialog.filename(); const auto upload_filename = upload_filepath.filename(); const auto upload_parent_path = upload_filepath.parent_path(); @@ -101,7 +101,7 @@ bool OctoPrint::send_gcode(const std::string &filename) const auto http = Http::post(std::move(url)); set_auth(http); http.form_add("print", print ? "true" : "false") - .form_add("path", upload_parent_path.string()) + .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? .form_add_file("file", filename, upload_filename.string()) .on_complete([&](std::string body, unsigned status) { BOOST_LOG_TRIVIAL(debug) << boost::format("Octoprint: File uploaded: HTTP %1%: %2%") % status % body; @@ -129,6 +129,11 @@ bool OctoPrint::send_gcode(const std::string &filename) const return res; } +bool OctoPrint::upload(PrintHostUpload upload_data) const +{ + throw "unimplemented"; +} + bool OctoPrint::has_auto_discovery() const { return true; diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index f6c4d58c8..314e4cfae 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -24,6 +24,7 @@ public: wxString get_test_failed_msg (wxString &msg) const; // Send gcode file to octoprint, filename is expected to be in UTF-8 bool send_gcode(const std::string &filename) const; + bool upload(PrintHostUpload upload_data) const; bool has_auto_discovery() const; bool can_test() const; private: diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index dd72bae40..570d72f68 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -1,7 +1,15 @@ #include "OctoPrint.hpp" #include "Duet.hpp" +#include +#include +#include + #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Channel.hpp" + +using boost::optional; + namespace Slic3r { @@ -10,13 +18,42 @@ PrintHost::~PrintHost() {} PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) { - PrintHostType kind = config->option>("host_type")->value; - if (kind == htOctoPrint) { - return new OctoPrint(config); - } else if (kind == htDuet) { - return new Duet(config); - } - return nullptr; + PrintHostType kind = config->option>("host_type")->value; + if (kind == htOctoPrint) { + return new OctoPrint(config); + } else if (kind == htDuet) { + return new Duet(config); + } + return nullptr; +} + + +struct PrintHostJobQueue::priv +{ + std::vector jobs; + Channel channel; + + std::thread bg_thread; + optional bg_job; +}; + +PrintHostJobQueue::PrintHostJobQueue() + : p(new priv()) +{ + std::shared_ptr p2 = p; + p->bg_thread = std::thread([p2]() { + // Wait for commands on the channel: + auto cmd = p2->channel.pop(); + // TODO + }); +} + +PrintHostJobQueue::~PrintHostJobQueue() +{ + // TODO: stop the thread + // if (p && p->bg_thread.joinable()) { + // p->bg_thread.detach(); + // } } diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index bc828ea46..2ccd1382a 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -8,26 +8,66 @@ namespace Slic3r { - class DynamicPrintConfig; + +struct PrintHostUpload +{ + boost::filesystem::path source_path; + boost::filesystem::path upload_path; + bool start_print = false; +}; + + class PrintHost { public: - virtual ~PrintHost(); + virtual ~PrintHost(); - virtual bool test(wxString &curl_msg) const = 0; - virtual wxString get_test_ok_msg () const = 0; - virtual wxString get_test_failed_msg (wxString &msg) const = 0; - // Send gcode file to print host, filename is expected to be in UTF-8 - virtual bool send_gcode(const std::string &filename) const = 0; - virtual bool has_auto_discovery() const = 0; - virtual bool can_test() const = 0; + virtual bool test(wxString &curl_msg) const = 0; + virtual wxString get_test_ok_msg () const = 0; + virtual wxString get_test_failed_msg (wxString &msg) const = 0; + // Send gcode file to print host, filename is expected to be in UTF-8 + virtual bool send_gcode(const std::string &filename) const = 0; // XXX: remove in favor of upload() + virtual bool upload(PrintHostUpload upload_data) const = 0; + virtual bool has_auto_discovery() const = 0; + virtual bool can_test() const = 0; - static PrintHost* get_print_host(DynamicPrintConfig *config); + static PrintHost* get_print_host(DynamicPrintConfig *config); }; +struct PrintHostJob +{ + PrintHostUpload upload_data; + std::unique_ptr printhost; + + PrintHostJob() {} + PrintHostJob(DynamicPrintConfig *config) + : printhost(PrintHost::get_print_host(config)) + {} + + bool empty() const { return !printhost; } + operator bool() const { return !!printhost; } +}; + + +class PrintHostJobQueue +{ +public: + PrintHostJobQueue(); + PrintHostJobQueue(const PrintHostJobQueue &) = delete; + PrintHostJobQueue(PrintHostJobQueue &&other) = delete; + ~PrintHostJobQueue(); + + PrintHostJobQueue& operator=(const PrintHostJobQueue &) = delete; + PrintHostJobQueue& operator=(PrintHostJobQueue &&other) = delete; + +private: + struct priv; + std::shared_ptr p; +}; + } diff --git a/src/slic3r/Utils/PrintHostSendDialog.cpp b/src/slic3r/Utils/PrintHostSendDialog.cpp deleted file mode 100644 index 84428d3b5..000000000 --- a/src/slic3r/Utils/PrintHostSendDialog.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "PrintHostSendDialog.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "slic3r/GUI/GUI.hpp" -#include "slic3r/GUI/MsgDialog.hpp" -#include "slic3r/GUI/I18N.hpp" - -namespace fs = boost::filesystem; - -namespace Slic3r { - -PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, bool can_start_print) : - MsgDialog(nullptr, _(L("Send G-Code to printer host")), _(L("Upload to Printer Host with the following filename:")), wxID_NONE), - txt_filename(new wxTextCtrl(this, wxID_ANY, path.filename().wstring())), - box_print(new wxCheckBox(this, wxID_ANY, _(L("Start printing after upload")))), - can_start_print(can_start_print) -{ - auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _(L("Use forward slashes ( / ) as a directory separator if needed."))); - label_dir_hint->Wrap(CONTENT_WIDTH); - - content_sizer->Add(txt_filename, 0, wxEXPAND); - content_sizer->Add(label_dir_hint); - content_sizer->AddSpacer(VERT_SPACING); - content_sizer->Add(box_print, 0, wxBOTTOM, 2*VERT_SPACING); - - btn_sizer->Add(CreateStdDialogButtonSizer(wxOK | wxCANCEL)); - - txt_filename->SetFocus(); - wxString stem(path.stem().wstring()); - txt_filename->SetSelection(0, stem.Length()); - - box_print->Enable(can_start_print); - - Fit(); -} - -fs::path PrintHostSendDialog::filename() const -{ - return fs::path(txt_filename->GetValue().wx_str()); -} - -bool PrintHostSendDialog::print() const -{ - return box_print->GetValue(); } -} From 2eaca46b75e7d49375509f5ad5ad47e2c9cdf392 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 12 Dec 2018 13:35:00 +0100 Subject: [PATCH 39/61] OctoPrint WIP: Fix build --- src/libslic3r/Channel.hpp | 104 ++++++++++++++++++++ src/slic3r/GUI/BackgroundSlicingProcess.cpp | 7 +- src/slic3r/Utils/PrintHost.hpp | 14 +++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/libslic3r/Channel.hpp diff --git a/src/libslic3r/Channel.hpp b/src/libslic3r/Channel.hpp new file mode 100644 index 000000000..8d1a07d35 --- /dev/null +++ b/src/libslic3r/Channel.hpp @@ -0,0 +1,104 @@ +#ifndef slic3r_Channel_hpp_ +#define slic3r_Channel_hpp_ + +#include +#include +#include +#include +#include + + +namespace Slic3r { + + +template class Channel +{ +private: + using UniqueLock = std::unique_lock; + using Queue = std::deque; +public: + class Guard + { + public: + Guard(UniqueLock lock, const Queue &queue) : m_lock(std::move(lock)), m_queue(queue) {} + Guard(const Guard &other) = delete; + Guard(Guard &&other) = delete; + ~Guard() {} + + // Access trampolines + size_t size() const noexcept { return m_queue.size(); } + bool empty() const noexcept { return m_queue.empty(); } + typename Queue::const_iterator begin() const noexcept { return m_queue.begin(); } + typename Queue::const_iterator end() const noexcept { return m_queue.end(); } + typename Queue::const_reference operator[](size_t i) const { return m_queue[i]; } + + Guard& operator=(const Guard &other) = delete; + Guard& operator=(Guard &&other) = delete; + private: + UniqueLock m_lock; + const Queue &m_queue; + }; + + + Channel() {} + ~Channel() {} + + void push(const T& item, bool silent = false) + { + { + UniqueLock lock(m_mutex); + m_queue.push_back(item); + } + if (! silent) { m_condition.notify_one(); } + } + + void push(T &&item, bool silent = false) + { + { + UniqueLock lock(m_mutex); + m_queue.push_back(std::forward(item)); + } + if (! silent) { m_condition.notify_one(); } + } + + T pop() + { + UniqueLock lock(m_mutex); + m_condition.wait(lock, [this]() { return !m_queue.empty(); }); + auto item = std::move(m_queue.front()); + m_queue.pop_front(); + return item; + } + + boost::optional try_pop() + { + UniqueLock lock(m_mutex); + if (m_queue.empty()) { + return boost::none; + } else { + auto item = std::move(m_queue.front()); + m_queue.pop(); + return item; + } + } + + // Unlocked observers + // Thread unsafe! Keep in mind you need to re-verify the result after acquiring lock! + size_t size() const noexcept { return m_queue.size(); } + bool empty() const noexcept { return m_queue.empty(); } + + Guard read() const + { + return Guard(UniqueLock(m_mutex), m_queue); + } + +private: + Queue m_queue; + std::mutex m_mutex; + std::condition_variable m_condition; +}; + + +} // namespace Slic3r + +#endif // slic3r_Channel_hpp_ diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 1829de959..3a34b3381 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -17,6 +17,7 @@ #include "libslic3r/GCode/PostProcessor.hpp" //#undef NDEBUG +#include // XXX #include #include #include @@ -79,6 +80,10 @@ void BackgroundSlicingProcess::process_fff() wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); if (this->set_step_started(bspsGCodeFinalize)) { + + std::cerr << "BackgroundSlicingProcess: m_upload_job: " << !!m_upload_job << std::endl; + std::cerr << "BackgroundSlicingProcess: m_export_path: " << m_export_path << std::endl; + if (! m_export_path.empty()) { //FIXME localize the messages // Perform the final post-processing of the export path by applying the print statistics over the file name. @@ -387,7 +392,7 @@ void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job) if (! m_export_path.empty()) return; - const auto path = boost::filesystem::temp_directory_path() + const boost::filesystem::path path = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(".upload.%%%%-%%%%-%%%%-%%%%.gcode"); // Guard against entering the export step before changing the export path. diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index 2ccd1382a..115807e9a 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -43,10 +43,24 @@ struct PrintHostJob std::unique_ptr printhost; PrintHostJob() {} + PrintHostJob(const PrintHostJob&) = delete; + PrintHostJob(PrintHostJob &&other) + : upload_data(std::move(other.upload_data)) + , printhost(std::move(other.printhost)) + {} + PrintHostJob(DynamicPrintConfig *config) : printhost(PrintHost::get_print_host(config)) {} + PrintHostJob& operator=(const PrintHostJob&) = delete; + PrintHostJob& operator=(PrintHostJob &&other) + { + upload_data = std::move(other.upload_data); + printhost = std::move(other.printhost); + return *this; + } + bool empty() const { return !printhost; } operator bool() const { return !!printhost; } }; From 4e7749a50dd8e35c68fe65d8d5ae9a65c89158ff Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 12 Dec 2018 13:56:38 +0100 Subject: [PATCH 40/61] OctoPrint WIP: Fix build --- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 3a34b3381..b31fa6b5f 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -17,7 +17,6 @@ #include "libslic3r/GCode/PostProcessor.hpp" //#undef NDEBUG -#include // XXX #include #include #include @@ -80,10 +79,6 @@ void BackgroundSlicingProcess::process_fff() wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id)); m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); if (this->set_step_started(bspsGCodeFinalize)) { - - std::cerr << "BackgroundSlicingProcess: m_upload_job: " << !!m_upload_job << std::endl; - std::cerr << "BackgroundSlicingProcess: m_export_path: " << m_export_path << std::endl; - if (! m_export_path.empty()) { //FIXME localize the messages // Perform the final post-processing of the export path by applying the print statistics over the file name. @@ -398,7 +393,7 @@ void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job) // Guard against entering the export step before changing the export path. tbb::mutex::scoped_lock lock(m_print->state_mutex()); this->invalidate_step(bspsGCodeFinalize); - m_export_path = path.native(); + m_export_path = path.string(); m_upload_job = std::move(upload_job); } From 5dd0d505ee4874a2be4bb3acf05c69eb1702e668 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 12 Dec 2018 14:15:08 +0100 Subject: [PATCH 41/61] Add missing include --- src/slic3r/Utils/PrintHost.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index 115807e9a..53f7c43d3 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -3,6 +3,8 @@ #include #include +#include + #include From 913cece5a604a2e53967af3cbd4f864013d601dc Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Wed, 12 Dec 2018 14:18:03 +0100 Subject: [PATCH 42/61] Plater: Remove extraneous button --- src/slic3r/GUI/Plater.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5ce49259a..67ea8c717 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -546,7 +546,6 @@ Sidebar::Sidebar(Plater *parent) p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxLEFT | wxTOP, 20); wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); - p->btn_send_gcode = new wxButton(p->scrolled, wxID_ANY, _(L("Send to printer"))); p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); p->btn_send_gcode->SetBitmap(arrow_up); p->btn_send_gcode->SetFont(wxGetApp().bold_font()); From f60a767ed9ce029f93702c7341a632f5c31a703b Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 14:40:56 +0100 Subject: [PATCH 43/61] Fix of the wipe tower manipulation: 3D scene should maintain selection status of the wipe tower after the wipe tower is updated. --- src/slic3r/GUI/GLCanvas3D.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index cc86b9b7a..74a472464 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4094,7 +4094,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_reload_delayed = ! m_canvas->IsShown() && ! refresh_immediately && ! force_full_scene_refresh; - PrinterTechnology printer_technology = m_process->current_printer_technology(); + PrinterTechnology printer_technology = m_process->current_printer_technology(); + int volume_idx_wipe_tower_old = -1; if (m_regenerate_volumes) { @@ -4152,6 +4153,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re } if (mvs == nullptr || force_full_scene_refresh) { // This GLVolume will be released. + if (volume->is_wipe_tower) { + // There is only one wipe tower. + assert(volume_idx_wipe_tower_old == -1); + volume_idx_wipe_tower_old = (int)volume_id; + } volume->release_geometry(); if (! m_reload_delayed) delete volume; @@ -4319,8 +4325,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re float depth = print->get_wipe_tower_depth(); if (!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, !print->is_step_done(psWipeTower), - print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( + 1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), + print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); + if (volume_idx_wipe_tower_old != -1) + map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; } } From e1e4bf74ba98e261616a1fa09466cf6a992e85c7 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Dec 2018 13:18:38 +0100 Subject: [PATCH 44/61] ObjectList: first column editing --- src/slic3r/GUI/GUI_ObjectList.cpp | 17 ++++++--------- src/slic3r/GUI/wxExtensions.cpp | 36 +++++++++++++++++++++++++++++++ src/slic3r/GUI/wxExtensions.hpp | 10 +++++++-- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 22ebc093f..bf7e7255a 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -337,9 +337,7 @@ void ObjectList::selection_changed() void ObjectList::OnChar(wxKeyEvent& event) { -// printf("KeyDown event\n"); if (event.GetKeyCode() == WXK_BACK){ - printf("WXK_BACK\n"); remove(); } else if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_SHIFT)) @@ -370,15 +368,14 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) if (title == " ") show_context_menu(); - - else if (title == _("Name") && pt.x >15 && - m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) - { - if (is_windows10()) { - const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); - wxGetApp().plater()->fix_through_netfabb(obj_idx); - } + else if (title == _("Name") && pt.x >15 && + m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) + { + if (is_windows10()) { + const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + wxGetApp().plater()->fix_through_netfabb(obj_idx); } + } #ifndef __WXMSW__ GetMainWindow()->SetToolTip(""); // hide tooltip #endif //__WXMSW__ diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 57828ea9a..8d94c964f 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1281,6 +1281,42 @@ wxSize PrusaBitmapTextRenderer::GetSize() const } +wxWindow* PrusaBitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); + PrusaObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); + + if ( !(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itObject)) ) + return nullptr; + + PrusaDataViewBitmapText data; + data << value; + m_bmp_from_editing_item = data.GetBitmap(); + + wxPoint position = labelRect.GetPosition(); + if (m_bmp_from_editing_item.IsOk()) { + const int bmp_width = m_bmp_from_editing_item.GetWidth(); + position.x += bmp_width; + labelRect.SetWidth(labelRect.GetWidth() - bmp_width); + } + + wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), + position, labelRect.GetSize(), wxTE_PROCESS_ENTER); + text_editor->SetInsertionPointEnd(); + + return text_editor; +} + +bool PrusaBitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); + if (!text_editor) + return false; + + value << PrusaDataViewBitmapText(text_editor->GetValue(), m_bmp_from_editing_item); + return true; +} + // ---------------------------------------------------------------------------- // PrusaDoubleSlider // ---------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 866317e25..97f753906 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -522,7 +522,7 @@ public: class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer { public: - PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT, + PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_EDITABLE, int align = wxDVR_DEFAULT_ALIGNMENT): wxDataViewCustomRenderer(wxT("PrusaDataViewBitmapText"), mode, align) {} @@ -532,10 +532,16 @@ public: virtual bool Render(wxRect cell, wxDC *dc, int state); virtual wxSize GetSize() const; - virtual bool HasEditorCtrl() const { return false; } + bool HasEditorCtrl() const override { return true; } + wxWindow* CreateEditorCtrl(wxWindow* parent, + wxRect labelRect, + const wxVariant& value) override; + bool GetValueFromEditorCtrl( wxWindow* ctrl, + wxVariant& value) override; private: PrusaDataViewBitmapText m_value; + wxBitmap m_bmp_from_editing_item; }; From 416f220c362d5f31803e33535f69b06c1875880f Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 12 Dec 2018 14:35:18 +0100 Subject: [PATCH 45/61] Save edited object/part's name to the object model --- src/slic3r/GUI/GUI_ObjectList.cpp | 46 ++++++++++++++++++++++++++----- src/slic3r/GUI/GUI_ObjectList.hpp | 3 ++ src/slic3r/GUI/wxExtensions.cpp | 12 +++++++- src/slic3r/GUI/wxExtensions.hpp | 2 ++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index bf7e7255a..7ed6e5a28 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -75,6 +75,8 @@ ObjectList::ObjectList(wxWindow* parent) : Bind(wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, &ObjectList::OnDropPossible, this); Bind(wxEVT_DATAVIEW_ITEM_DROP, &ObjectList::OnDrop, this); + Bind(wxEVT_DATAVIEW_ITEM_EDITING_DONE, &ObjectList::OnEditingDone, this); + Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &ObjectList::ItemValueChanged, this); Bind(wxCUSTOMEVT_LAST_VOLUME_IS_DELETED, [this](wxCommandEvent& e) { last_volume_is_deleted(e.GetInt()); }); @@ -290,6 +292,21 @@ void ObjectList::update_extruder_in_config(const wxDataViewItem& item) wxGetApp().plater()->update(); } +void ObjectList::update_name_in_model(const wxDataViewItem& item) +{ + const int obj_idx = m_objects_model->GetObjectIdByItem(item); + if (obj_idx < 0) return; + + if (m_objects_model->GetParent(item) == wxDataViewItem(0)) { + (*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToStdString(); + return; + } + + const int volume_id = m_objects_model->GetVolumeIdByItem(item); + if (volume_id < 0) return; + (*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToStdString(); +} + void ObjectList::init_icons() { m_bmp_modifiermesh = wxBitmap(from_u8(var("lambda.png")), wxBITMAP_TYPE_PNG);//(Slic3r::var("plugin.png")), wxBITMAP_TYPE_PNG); @@ -452,10 +469,10 @@ void ObjectList::OnDropPossible(wxDataViewEvent &event) wxDataViewItem item(event.GetItem()); // only allow drags for item or background, not containers - if (item.IsOk() && - (m_objects_model->GetParent(item) == wxDataViewItem(0) || + if (!item.IsOk() || + m_objects_model->GetParent(item) == wxDataViewItem(0) || m_objects_model->GetItemType(item) != itVolume || - m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item))) + m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) event.Veto(); } @@ -463,9 +480,9 @@ void ObjectList::OnDrop(wxDataViewEvent &event) { wxDataViewItem item(event.GetItem()); - if (item.IsOk() && ( m_objects_model->GetParent(item) == wxDataViewItem(0) || - m_objects_model->GetItemType(item) != itVolume) || - m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) { + if (!item.IsOk() || m_objects_model->GetParent(item) == wxDataViewItem(0) || + m_objects_model->GetItemType(item) != itVolume || + m_dragged_data.obj_idx() != m_objects_model->GetObjectIdByItem(item)) { event.Veto(); m_dragged_data.clear(); return; @@ -1664,7 +1681,22 @@ void ObjectList::update_settings_items() void ObjectList::ItemValueChanged(wxDataViewEvent &event) { - update_extruder_in_config(event.GetItem()); + if (event.GetColumn() == 0) + update_name_in_model(event.GetItem()); + else if (event.GetColumn() == 1) + update_extruder_in_config(event.GetItem()); +} + +void ObjectList::OnEditingDone(wxDataViewEvent &event) +{ + if (event.GetColumn() != 0) + return; + + const auto renderer = dynamic_cast(GetColumn(0)->GetRenderer()); + + if (renderer->WasCanceled()) + show_error(this, _(L("The supplied name is not valid;")) + "\n" + + _(L("the following characters are not allowed:")) + " <>:/\\|?*\""); } } //namespace GUI diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 33e3894fe..3664e6fda 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -128,6 +128,8 @@ public: void set_extruder_column_hidden(const bool hide) const; // update extruder in current config void update_extruder_in_config(const wxDataViewItem& item); + // update changed name in the object model + void update_name_in_model(const wxDataViewItem& item); void update_extruder_values_for_items(const int max_extruder); void init_icons(); @@ -227,6 +229,7 @@ private: void OnDrop(wxDataViewEvent &event); void ItemValueChanged(wxDataViewEvent &event); + void OnEditingDone(wxDataViewEvent &event); }; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 8d94c964f..2e5a37040 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -1292,6 +1292,7 @@ wxWindow* PrusaBitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect lab PrusaDataViewBitmapText data; data << value; m_bmp_from_editing_item = data.GetBitmap(); + m_was_unusable_symbol = false; wxPoint position = labelRect.GetPosition(); if (m_bmp_from_editing_item.IsOk()) { @@ -1310,9 +1311,18 @@ wxWindow* PrusaBitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect lab bool PrusaBitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) { wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); - if (!text_editor) + if (!text_editor || text_editor->GetValue().IsEmpty()) return false; + std::string chosen_name = Slic3r::normalize_utf8_nfc(text_editor->GetValue().ToUTF8()); + const char* unusable_symbols = "<>:/\\|?*\""; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + m_was_unusable_symbol = true; + return false; + } + } + value << PrusaDataViewBitmapText(text_editor->GetValue(), m_bmp_from_editing_item); return true; } diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 97f753906..3fbf9a083 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -538,10 +538,12 @@ public: const wxVariant& value) override; bool GetValueFromEditorCtrl( wxWindow* ctrl, wxVariant& value) override; + bool WasCanceled() const { return m_was_unusable_symbol; } private: PrusaDataViewBitmapText m_value; wxBitmap m_bmp_from_editing_item; + bool m_was_unusable_symbol; }; From b8939ed7df4b4e47d0323d806cbde6a7679ef471 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 15:09:20 +0100 Subject: [PATCH 46/61] Print time in output G-code: fill in the time if the file export dialog is open after the slicing finished. --- src/libslic3r/Print.cpp | 48 +++++++++++++++++++-- src/libslic3r/Print.hpp | 7 +++ src/slic3r/GUI/BackgroundSlicingProcess.cpp | 27 +----------- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 3bbe00805..4d509f112 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -14,6 +14,8 @@ #include "PrintExport.hpp" +#include + //! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) @@ -1864,14 +1866,54 @@ std::string Print::output_filename() const { // Set the placeholders for the data know first after the G-code export is finished. // These values will be just propagated into the output file name. + DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); + return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config); +} + +DynamicConfig PrintStatistics::config() const +{ + DynamicConfig config; + std::string normal_print_time = this->estimated_normal_print_time; + std::string silent_print_time = this->estimated_silent_print_time; + normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), std::isspace), normal_print_time.end()); + silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), std::isspace), silent_print_time.end()); + config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); + config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); + config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament)); + config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume)); + config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost)); + config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight)); + config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost)); + config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament)); + return config; +} + +DynamicConfig PrintStatistics::placeholders() +{ DynamicConfig config; for (const std::string &key : { "print_time", "normal_print_time", "silent_print_time", "used_filament", "extruded_volume", "total_cost", "total_weight", "total_wipe_tower_cost", "total_wipe_tower_filament"}) - config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); - return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config); + config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); + return config; +} + +std::string PrintStatistics::finalize_output_path(const std::string &path_in) const +{ + std::string final_path; + try { + boost::filesystem::path path(path_in); + DynamicConfig cfg = this->config(); + PlaceholderParser pp; + std::string new_stem = pp.process(path.stem().string(), 0, &cfg); + final_path = (path.parent_path() / (new_stem + path.extension().string())).string(); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what(); + final_path = path_in; + } + return final_path; } } // namespace Slic3r - diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 27ee6f3c2..1b79ef295 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -249,6 +249,13 @@ struct PrintStatistics double total_wipe_tower_filament; std::map filament_stats; + // Config with the filled in print statistics. + DynamicConfig config() const; + // Config with the statistics keys populated with placeholder strings. + static DynamicConfig placeholders(); + // Replace the print statistics placeholders in the path. + std::string finalize_output_path(const std::string &path_in) const; + void clear() { estimated_normal_print_time.clear(); estimated_silent_print_time.clear(); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index b31fa6b5f..b9f146013 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -82,32 +82,7 @@ void BackgroundSlicingProcess::process_fff() if (! m_export_path.empty()) { //FIXME localize the messages // Perform the final post-processing of the export path by applying the print statistics over the file name. - std::string export_path; - { - const PrintStatistics &stats = m_fff_print->print_statistics(); - PlaceholderParser pp; - std::string normal_print_time = stats.estimated_normal_print_time; - std::string silent_print_time = stats.estimated_silent_print_time; - normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), isspace), normal_print_time.end()); - silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), isspace), silent_print_time.end()); - pp.set("print_time", new ConfigOptionString(normal_print_time)); - pp.set("normal_print_time", new ConfigOptionString(normal_print_time)); - pp.set("silent_print_time", new ConfigOptionString(silent_print_time)); - pp.set("used_filament", new ConfigOptionFloat (stats.total_used_filament)); - pp.set("extruded_volume", new ConfigOptionFloat (stats.total_extruded_volume)); - pp.set("total_cost", new ConfigOptionFloat (stats.total_cost)); - pp.set("total_weight", new ConfigOptionFloat (stats.total_weight)); - pp.set("total_wipe_tower_cost", new ConfigOptionFloat (stats.total_wipe_tower_cost)); - pp.set("total_wipe_tower_filament", new ConfigOptionFloat (stats.total_wipe_tower_filament)); - boost::filesystem::path path(m_export_path); - try { - std::string new_stem = pp.process(path.stem().string(), 0); - export_path = (path.parent_path() / (new_stem + path.extension().string())).string(); - } catch (const std::exception &ex) { - BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what(); - export_path = m_export_path; - } - } + std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path); if (copy_file(m_temp_output_path, export_path) != 0) throw std::runtime_error("Copying of the temporary G-code to the output G-code failed"); m_print->set_status(95, "Running post-processing scripts"); From 598e6f648bb9173df0c1a3ee3ed2f3437d577806 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 15:48:39 +0100 Subject: [PATCH 47/61] Time to print into the G-code file name: Round it to full minutes. --- src/libslic3r/Print.cpp | 46 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4d509f112..4858f5886 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1870,13 +1870,51 @@ std::string Print::output_filename() const return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config); } +// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes +// and removing spaces. +static std::string short_time(const std::string &time) +{ + // Parse the dhms time format. + int days = 0; + int hours = 0; + int minutes = 0; + int seconds = 0; + if (time.find('d') != std::string::npos) + ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds); + else if (time.find('h') != std::string::npos) + ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds); + else if (time.find('m') != std::string::npos) + ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds); + else if (time.find('s') != std::string::npos) + ::sscanf(time.c_str(), "%ds", &seconds); + // Round to full minutes. + if (days + hours + minutes > 0 && seconds >= 30) { + if (++ minutes == 60) { + minutes = 0; + if (++ hours == 24) { + hours = 0; + ++ days; + } + } + } + // Format the dhm time. + char buffer[64]; + if (days > 0) + ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes); + else if (hours > 0) + ::sprintf(buffer, "%dh%dm", hours, minutes); + else if (minutes > 0) + ::sprintf(buffer, "%dm", minutes); + else + ::sprintf(buffer, "%ds", seconds); + return buffer; +} + DynamicConfig PrintStatistics::config() const { DynamicConfig config; - std::string normal_print_time = this->estimated_normal_print_time; - std::string silent_print_time = this->estimated_silent_print_time; - normal_print_time.erase(std::remove_if(normal_print_time.begin(), normal_print_time.end(), std::isspace), normal_print_time.end()); - silent_print_time.erase(std::remove_if(silent_print_time.begin(), silent_print_time.end(), std::isspace), silent_print_time.end()); + std::string normal_print_time = short_time(this->estimated_normal_print_time); + std::string silent_print_time = short_time(this->estimated_silent_print_time); config.set_key_value("print_time", new ConfigOptionString(normal_print_time)); config.set_key_value("normal_print_time", new ConfigOptionString(normal_print_time)); config.set_key_value("silent_print_time", new ConfigOptionString(silent_print_time)); From 623b2cec5c2f2ee660694d1c67b476224cb93688 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Dec 2018 15:51:39 +0100 Subject: [PATCH 48/61] Another fix for crashing support generation. --- src/libslic3r/SLA/SLABasePool.cpp | 7 +++++-- src/libslic3r/SLAPrint.cpp | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 0e7dd3d06..21bd124f7 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -447,8 +447,11 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, ExPolygons tmp; tmp.reserve(count); for(auto& o : out) for(auto& e : o) tmp.emplace_back(std::move(e)); - output = unify(tmp); - for(auto& o : output) o = o.simplify(0.1/SCALING_FACTOR).front(); + ExPolygons utmp = unify(tmp); + for(auto& o : utmp) { + auto&& smp = o.simplify(0.1/SCALING_FACTOR); + output.insert(output.end(), smp.begin(), smp.end()); + } } void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 07de3596a..3cd1d0a24 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -469,7 +469,7 @@ void SLAPrint::process() for(float h = minZ + ilh; h < maxZ; h += flh) if(h >= gnd) heights.emplace_back(h); - auto& layers = po.m_model_slices; + auto& layers = po.m_model_slices; layers.clear(); slicer.slice(heights, &layers, [this](){ throw_if_canceled(); }); }; From 80d0ca3ec03ccaa46d10ea69f2607ba4fa031bfe Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 18:37:10 +0100 Subject: [PATCH 49/61] [Feature] An option to export non manufacturer printer/filament/print profiles. #1292 https://github.com/prusa3d/Slic3r/issues/1292 --- src/slic3r/GUI/PresetBundle.cpp | 15 ++++++++------- src/slic3r/GUI/PresetBundle.hpp | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 166a4e9e9..ee7ba279f 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -1313,7 +1313,7 @@ void PresetBundle::update_compatible(bool select_other_if_incompatible) } } -void PresetBundle::export_configbundle(const std::string &path) //, const DynamicPrintConfig &settings +void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings) { boost::nowide::ofstream c; c.open(path, std::ios::out | std::ios::trunc); @@ -1323,14 +1323,15 @@ void PresetBundle::export_configbundle(const std::string &path) //, const Dynami // Export the print, filament and printer profiles. - // #ys_FIXME_SLA_PRINT - for (size_t i_group = 0; i_group < 3; ++ i_group) { - const PresetCollection &presets = (i_group == 0) ? this->prints : (i_group == 1) ? this->filaments : this->printers; - for (const Preset &preset : presets()) { - if (preset.is_default || preset.is_external) + for (const PresetCollection *presets : { + (const PresetCollection*)&this->prints, (const PresetCollection*)&this->filaments, + (const PresetCollection*)&this->sla_prints, (const PresetCollection*)&this->sla_materials, + (const PresetCollection*)&this->printers }) { + for (const Preset &preset : (*presets)()) { + if (preset.is_default || preset.is_external || (preset.is_system && ! export_system_settings)) // Only export the common presets, not external files or the default preset. continue; - c << std::endl << "[" << presets.name() << ":" << preset.name << "]" << std::endl; + c << std::endl << "[" << presets->name() << ":" << preset.name << "]" << std::endl; for (const std::string &opt_key : preset.config.keys()) c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl; } diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index 1bea146dd..9f289f1e9 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -102,7 +102,7 @@ public: size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); // Export a config bundle file containing all the presets and the names of the active presets. - void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings); + void export_configbundle(const std::string &path, bool export_system_settings = false); // Update a filament selection combo box on the platter for an idx_extruder. void update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui); From 9917edcdcc3db062eed5c64eb407036df37a9d28 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 12 Dec 2018 18:45:45 +0100 Subject: [PATCH 50/61] XY flip is implemented, works only for portrait LCD. --- src/libslic3r/PrintExport.hpp | 6 ++---- src/libslic3r/Rasterizer/Rasterizer.cpp | 22 +++++++++++++--------- src/libslic3r/Rasterizer/Rasterizer.hpp | 7 +++++++ src/libslic3r/SLAPrint.cpp | 12 ++++++++++-- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index 0a9a54187..3f96cfaea 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -31,8 +31,6 @@ template class FilePrinter { public: - void print_config(const Print&); - // Draw an ExPolygon which is a polygon inside a slice on the specified layer. void draw_polygon(const ExPolygon& p, unsigned lyr); @@ -147,8 +145,8 @@ template<> class FilePrinter +layerh_str+"+printer=DWARF3\n"; } - // Change this to TOP_LEFT if you want correct PNG orientation - static const Raster::Origin ORIGIN = Raster::Origin::BOTTOM_LEFT; + // The PNG format has its origin in the top left corner. + static const Raster::Origin ORIGIN = Raster::Origin::TOP_LEFT; public: inline FilePrinter(double width_mm, double height_mm, diff --git a/src/libslic3r/Rasterizer/Rasterizer.cpp b/src/libslic3r/Rasterizer/Rasterizer.cpp index 3ff3e0949..621b76b08 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.cpp +++ b/src/libslic3r/Rasterizer/Rasterizer.cpp @@ -45,15 +45,20 @@ private: TRawRenderer m_raw_renderer; TRendererAA m_renderer; Origin m_o; - std::function m_flipy = [](agg::path_storage&) {}; + + inline void flipy(agg::path_storage& path) const { + path.flip_y(0, m_resolution.height_px); + } + public: + inline Impl(const Raster::Resolution& res, const Raster::PixelDim &pd, Origin o): m_resolution(res), m_pxdim(pd), m_buf(res.pixels()), m_rbuf(reinterpret_cast(m_buf.data()), res.width_px, res.height_px, - res.width_px*TPixelRenderer::num_components), + int(res.width_px*TPixelRenderer::num_components)), m_pixfmt(m_rbuf), m_raw_renderer(m_pixfmt), m_renderer(m_raw_renderer), @@ -65,10 +70,6 @@ public: // ras.gamma(agg::gamma_power(1.0)); clear(); - - if(m_o == Origin::TOP_LEFT) m_flipy = [this](agg::path_storage& path) { - path.flip_y(0, m_resolution.height_px); - }; } void draw(const ExPolygon &poly) { @@ -76,12 +77,14 @@ public: agg::scanline_p8 scanlines; auto&& path = to_path(poly.contour); - m_flipy(path); + + if(m_o == Origin::TOP_LEFT) flipy(path); + ras.add_path(path); for(auto h : poly.holes) { auto&& holepath = to_path(h); - m_flipy(holepath); + if(m_o == Origin::TOP_LEFT) flipy(holepath); ras.add_path(holepath); } @@ -205,8 +208,9 @@ void Raster::save(std::ostream& stream, Compression comp) << m_impl->resolution().height_px << " " << "255 "; + auto sz = m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type); stream.write(reinterpret_cast(m_impl->buffer().data()), - m_impl->buffer().size()*sizeof(Impl::TBuffer::value_type)); + std::streamsize(sz)); } } } diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/Rasterizer/Rasterizer.hpp index b6406b770..06d5b88c6 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.hpp +++ b/src/libslic3r/Rasterizer/Rasterizer.hpp @@ -27,6 +27,13 @@ public: PNG //!> PNG compression }; + /// The Rasterizer expects the input polygons to have their coordinate + /// system origin in the bottom left corner. If the raster is then + /// configured with the TOP_LEFT origin parameter (in the constructor) than + /// it will flip the Y axis in output to maintain the correct orientation. + /// This is the default case with PNG images. They have the origin in the + /// top left corner. Without the flipping, the image would be upside down + /// with the scaled (clipper) coordinate system of the input polygons. enum class Origin { TOP_LEFT, BOTTOM_LEFT diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 3cd1d0a24..2aed15273 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -715,6 +715,8 @@ void SLAPrint::process() std::vector keys; keys.reserve(levels.size()); for(auto& e : levels) keys.emplace_back(e.first); + bool flpXY = m_printer_config.display_flip_xy.getBool(); + { // create a raster printer for the current print parameters // I don't know any better auto& ocfg = m_objects.front()->m_config; @@ -729,6 +731,8 @@ void SLAPrint::process() double exp_t = matcfg.exposure_time.getFloat(); double iexp_t = matcfg.initial_exposure_time.getFloat(); + if(flpXY) { std::swap(w, h); std::swap(pw, ph); } + m_printer.reset(new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t)); } @@ -744,7 +748,7 @@ void SLAPrint::process() // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &keys, &levels, &printer, slot, sd, ist, &pst] + [this, &slck, &keys, &levels, &printer, slot, sd, ist, &pst, flpXY] (unsigned level_id) { if(canceled()) return; @@ -764,8 +768,12 @@ void SLAPrint::process() for(ExPolygon slice : sl) { // The order is important here: // apply rotation before translation... - slice.rotate(cp.rotation); + slice.rotate(double(cp.rotation)); slice.translate(cp.shift(X), cp.shift(Y)); + if(flpXY) { + for(auto& p : slice.contour.points) std::swap(p(X), p(Y)); + for(auto& h : slice.holes) for(auto& p : h.points) std::swap(p(X), p(Y)); + } printer.draw_polygon(slice, level_id); } } From 31dc6491901c64b19fab1b6db1b3836d3da4f7ad Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 19:02:18 +0100 Subject: [PATCH 51/61] Fix of https://github.com/prusa3d/Slic3r/issues/1298 Don't load preset files, if they are marked as hidden or system (Windows) --- src/slic3r/GUI/Preset.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 182181e70..4582e83ba 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -5,6 +5,12 @@ #include "BitmapCache.hpp" #include "I18N.hpp" +#ifdef _MSC_VER + #define WIN32_LEAN_AND_MEAN + #define NOMINMAX + #include +#endif /* _MSC_VER */ + #include #include #include @@ -13,6 +19,7 @@ #include #include +#include #include #include #include @@ -499,6 +506,16 @@ void PresetCollection::add_default_preset(const std::vector &keys, ++ m_num_default_presets; } +bool is_file_plain(const std::string &path) +{ +#ifdef _MSC_VER + DWORD attributes = GetFileAttributesW(boost::nowide::widen(path).c_str()); + return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0; +#else + return true; +#endif +} + // Load all presets found in dir_path. // Throws an exception on error. void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) @@ -507,7 +524,10 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri m_dir_path = dir.string(); std::string errors_cummulative; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) - if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini") && + // Ignore system and hidden files, which may be created by the DropBox synchronisation process. + // https://github.com/prusa3d/Slic3r/issues/1298 + is_file_plain(dir_entry.path().string())) { std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); From b856775e188da6d31bc7e53926d6f3248bec85b0 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 12 Dec 2018 19:09:25 +0100 Subject: [PATCH 52/61] Enabled the Wipe Tower for Repetier-Firmware #1310 --- src/libslic3r/Print.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4858f5886..77cf9c0f6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1228,8 +1228,8 @@ std::string Print::validate() const } if (this->has_wipe_tower() && ! m_objects.empty()) { - if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfMarlin) - return L("The Wipe Tower is currently only supported for the Marlin and RepRap/Sprinter G-code flavors."); + if (m_config.gcode_flavor != gcfRepRap && m_config.gcode_flavor != gcfRepetier && m_config.gcode_flavor != gcfMarlin) + return L("The Wipe Tower is currently only supported for the Marlin, RepRap/Sprinter and Repetier G-code flavors."); if (! m_config.use_relative_e_distances) return L("The Wipe Tower is currently only supported with the relative extruder addressing (use_relative_e_distances=1)."); SlicingParameters slicing_params0 = m_objects.front()->slicing_parameters(); From 3bddf2afff064a28c79f236ccf4616a33d649648 Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 13 Dec 2018 08:55:19 +0100 Subject: [PATCH 53/61] Selection's debug output set as optional --- src/libslic3r/Technologies.hpp | 2 ++ src/slic3r/GUI/GLCanvas3D.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 2bd3e2b2a..cef735db0 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -7,6 +7,8 @@ // Shows camera target in the 3D scene #define ENABLE_SHOW_CAMERA_TARGET 0 +// Log debug messages to console when changing selection +#define ENABLE_SELECTION_DEBUG_OUTPUT 0 //============= // 1.42.0 techs diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 74a472464..96d9ac4d5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2116,6 +2116,7 @@ void GLCanvas3D::Selection::_update_type() v->disabled = requires_disable ? (v->object_idx() != object_idx) || (v->instance_idx() != instance_idx) : false; } +#if ENABLE_SELECTION_DEBUG_OUTPUT std::cout << "Selection: "; std::cout << "mode: "; switch (m_mode) @@ -2197,6 +2198,7 @@ void GLCanvas3D::Selection::_update_type() break; } } +#endif // ENABLE_SELECTION_DEBUG_OUTPUT } void GLCanvas3D::Selection::_set_caches() From 1dad58e60ced0834f4c1e00b52743915a55fcd4d Mon Sep 17 00:00:00 2001 From: Enrico Turri Date: Thu, 13 Dec 2018 11:13:58 +0100 Subject: [PATCH 54/61] Fixed rendering of legend texture with new background color --- src/slic3r/GUI/GLCanvas3D.cpp | 50 ++++++++++++++++++++++++----------- src/slic3r/GUI/GLCanvas3D.hpp | 5 ++-- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 96d9ac4d5..352ea2f49 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3181,7 +3181,8 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const } const unsigned char GLCanvas3D::LegendTexture::Squares_Border_Color[3] = { 64, 64, 64 }; -const unsigned char GLCanvas3D::LegendTexture::Background_Color[3] = { 9, 91, 134 }; +const unsigned char GLCanvas3D::LegendTexture::Default_Background_Color[3] = { (unsigned char)(DEFAULT_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(DEFAULT_BG_LIGHT_COLOR[2] * 255.0f) }; +const unsigned char GLCanvas3D::LegendTexture::Error_Background_Color[3] = { (unsigned char)(ERROR_BG_LIGHT_COLOR[0] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[1] * 255.0f), (unsigned char)(ERROR_BG_LIGHT_COLOR[2] * 255.0f) }; const unsigned char GLCanvas3D::LegendTexture::Opacity = 255; GLCanvas3D::LegendTexture::LegendTexture() @@ -3191,7 +3192,7 @@ GLCanvas3D::LegendTexture::LegendTexture() { } -bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas) +bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool use_error_colors) { reset(); @@ -3230,8 +3231,11 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c return false; wxMemoryDC memDC; + wxMemoryDC mask_memDC; + // select default font memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + mask_memDC.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); // calculates texture size wxCoord w, h; @@ -3260,16 +3264,28 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c // generates bitmap wxBitmap bitmap(m_width, m_height); + wxBitmap mask(m_width, m_height); memDC.SelectObject(bitmap); - memDC.SetBackground(wxBrush(wxColour(Background_Color[0], Background_Color[1], Background_Color[2]))); + mask_memDC.SelectObject(mask); + + memDC.SetBackground(wxBrush(use_error_colors ? *wxWHITE : *wxBLACK)); + mask_memDC.SetBackground(wxBrush(*wxBLACK)); + memDC.Clear(); + mask_memDC.Clear(); // draw title - memDC.SetTextForeground(*wxWHITE); + memDC.SetTextForeground(use_error_colors ? *wxWHITE : *wxBLACK); + mask_memDC.SetTextForeground(*wxWHITE); + int title_x = Px_Border; int title_y = Px_Border; memDC.DrawText(title, title_x, title_y); + mask_memDC.DrawText(title, title_x, title_y); + + mask_memDC.SetPen(wxPen(*wxWHITE)); + mask_memDC.SetBrush(wxBrush(*wxWHITE)); // draw icons contours as background int squares_contour_x = Px_Border; @@ -3285,6 +3301,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c memDC.SetPen(pen); memDC.SetBrush(brush); memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height)); + mask_memDC.DrawRectangle(wxRect(squares_contour_x, squares_contour_y, squares_contour_width, squares_contour_height)); // draw items (colored icon + text) int icon_x = squares_contour_x + Px_Square_Contour; @@ -3321,16 +3338,18 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c // draw text memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset); + mask_memDC.DrawText(GUI::from_u8(item.text), text_x, icon_y + text_y_offset); // update y icon_y += icon_y_step; } memDC.SelectObject(wxNullBitmap); + mask_memDC.SelectObject(wxNullBitmap); // Convert the bitmap into a linear data ready to be loaded into the GPU. wxImage image = bitmap.ConvertToImage(); - image.SetMaskColour(Background_Color[0], Background_Color[1], Background_Color[2]); + wxImage mask_image = mask.ConvertToImage(); // prepare buffer std::vector data(4 * m_width * m_height, 0); @@ -3343,7 +3362,7 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c *px_ptr++ = image.GetRed(w, h); *px_ptr++ = image.GetGreen(w, h); *px_ptr++ = image.GetBlue(w, h); - *px_ptr++ = image.IsTransparent(w, h) ? 0 : Opacity; + *px_ptr++ = (mask_image.GetRed(w, h) + mask_image.GetGreen(w, h) + mask_image.GetBlue(w, h)) / 3; } } @@ -4395,10 +4414,10 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const return; #endif // !ENABLE_USE_UNIQUE_GLCONTEXT + std::vector tool_colors = _parse_colors(str_tool_colors); + if (m_volumes.empty()) { - std::vector tool_colors = _parse_colors(str_tool_colors); - m_gcode_preview_volume_index.reset(); _load_gcode_extrusion_paths(preview_data, tool_colors); @@ -4406,12 +4425,8 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const _load_gcode_retractions(preview_data); _load_gcode_unretractions(preview_data); - if (m_volumes.empty()) - reset_legend_texture(); - else + if (!m_volumes.empty()) { - _generate_legend_texture(preview_data, tool_colors); - // removes empty volumes m_volumes.volumes.erase(std::remove_if(m_volumes.volumes.begin(), m_volumes.volumes.end(), [](const GLVolume* volume) { return volume->print_zs.empty(); }), m_volumes.volumes.end()); @@ -4423,6 +4438,11 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const _update_gcode_volumes_visibility(preview_data); _show_warning_texture_if_needed(); + + if (m_volumes.empty()) + reset_legend_texture(); + else + _generate_legend_texture(preview_data, tool_colors); } } @@ -5819,7 +5839,7 @@ void GLCanvas3D::_render_background() const ::glPushMatrix(); ::glLoadIdentity(); - // Draws a bluish bottom to top gradient over the complete screen. + // Draws a bottom to top gradient over the complete screen. ::glDisable(GL_DEPTH_TEST); ::glBegin(GL_QUADS); @@ -7657,7 +7677,7 @@ void GLCanvas3D::_generate_legend_texture(const GCodePreviewData& preview_data, return; #endif // !ENABLE_USE_UNIQUE_GLCONTEXT - m_legend_texture.generate(preview_data, tool_colors, *this); + m_legend_texture.generate(preview_data, tool_colors, *this, m_dynamic_background_enabled && _is_any_volume_outside()); } void GLCanvas3D::_generate_warning_texture(const std::string& msg) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index fc284c595..12a83eebd 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -739,7 +739,8 @@ private: static const int Px_Square_Contour = 1; static const int Px_Border = Px_Square / 2; static const unsigned char Squares_Border_Color[3]; - static const unsigned char Background_Color[3]; + static const unsigned char Default_Background_Color[3]; + static const unsigned char Error_Background_Color[3]; static const unsigned char Opacity; int m_original_width; @@ -748,7 +749,7 @@ private: public: LegendTexture(); - bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas); + bool generate(const GCodePreviewData& preview_data, const std::vector& tool_colors, const GLCanvas3D& canvas, bool use_error_colors); void render(const GLCanvas3D& canvas) const; }; From 310adc18c6f51480b772bd8f456eb3908974c932 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Dec 2018 12:42:45 +0100 Subject: [PATCH 55/61] Removed display_flip_xy and added display_orientation instead. When starting Slic3r and the profile is FDM type than it yields an assertion failure for wx. See Tab::update_page_tree_visibility() line 2371 --- src/libslic3r/PrintConfig.cpp | 14 ++++++----- src/libslic3r/PrintConfig.hpp | 18 ++++++++++++-- src/libslic3r/PrintExport.hpp | 37 ++++++++++++++++++++++------ src/libslic3r/SLA/SLASupportTree.cpp | 6 ++--- src/libslic3r/SLAPrint.cpp | 19 +++++++++----- src/slic3r/GUI/OptionsGroup.cpp | 3 +++ src/slic3r/GUI/Preset.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 2 +- 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 6b40f50bf..ab26f5d54 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2389,12 +2389,14 @@ void PrintConfigDef::init_sla_params() def->min = 100; def->default_value = new ConfigOptionInt(1440); - def = this->add("display_flip_xy", coBool); - def->label = ("Flip X and Y axis"); - def->tooltip = L("Flip X and Y axis in the output raster"); - def->cli = "display-flip-xy=i"; - def->min = 0; - def->default_value = new ConfigOptionBool(true); + def = this->add("display_orientation", coEnum); + def->label = L("Display orientation"); + def->tooltip = L("Display orientation"); + def->cli = "display-orientation=s"; + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("Landscape"); + def->enum_values.push_back("Portrait"); + def->default_value = new ConfigOptionEnum(sladoPortrait); def = this->add("printer_correction", coFloats); def->full_label = L("Printer scaling correction"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c3d17c451..f639cad76 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -56,6 +56,11 @@ enum FilamentType { ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA }; +enum SLADisplayOrientation { + sladoLandscape, + sladoPortrait +}; + template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { static t_config_enum_values keys_map; if (keys_map.empty()) { @@ -148,6 +153,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::ge return keys_map; } +template<> inline const t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static const t_config_enum_values keys_map = { + { "Landscape", sladoLandscape}, + { "Portrait", sladoPortrait} + }; + + return keys_map; +} + // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. // Does not store the actual values, but defines default values. class PrintConfigDef : public ConfigDef @@ -1035,7 +1049,7 @@ public: ConfigOptionFloat display_height; ConfigOptionInt display_pixels_x; ConfigOptionInt display_pixels_y; - ConfigOptionBool display_flip_xy; + ConfigOptionEnum display_orientation; ConfigOptionFloats printer_correction; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) @@ -1047,7 +1061,7 @@ protected: OPT_PTR(display_height); OPT_PTR(display_pixels_x); OPT_PTR(display_pixels_y); - OPT_PTR(display_flip_xy); + OPT_PTR(display_orientation); OPT_PTR(printer_correction); } }; diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index 3f96cfaea..8187d7bf0 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -116,6 +116,7 @@ template<> class FilePrinter Raster::PixelDim m_pxdim; double m_exp_time_s = .0, m_exp_time_first_s = .0; double m_layer_height = .0; + Raster::Origin m_o = Raster::Origin::TOP_LEFT; std::string createIniContent(const std::string& projectname) { double layer_height = m_layer_height; @@ -145,19 +146,41 @@ template<> class FilePrinter +layerh_str+"+printer=DWARF3\n"; } - // The PNG format has its origin in the top left corner. - static const Raster::Origin ORIGIN = Raster::Origin::TOP_LEFT; - public: + + enum RasterOrientation { + RO_LANDSCAPE, + RO_PORTRAIT + }; + + // We will play with the raster's coordinate origin parameter. When the + // printer should print in landscape mode it should have the Y axis flipped + // because the layers should be displayed upside down. PNG has its + // coordinate origin in the top-left corner so normally the Raster objects + // should be instantiated with the TOP_LEFT flag. However, in landscape mode + // we do want the pictures to be upside down so we will make BOTTOM_LEFT + // type rasters and the PNG format will do the flipping automatically. + + // In case of portrait images, we have to rotate the image by a 90 degrees + // and flip the y axis. To get the correct upside-down orientation of the + // slice images, we can flip the x and y coordinates of the input polygons + // and do the Y flipping of the image. This will generate the correct + // orientation in portrait mode. + inline FilePrinter(double width_mm, double height_mm, unsigned width_px, unsigned height_px, double layer_height, - double exp_time, double exp_time_first): + double exp_time, double exp_time_first, + RasterOrientation ro = RO_PORTRAIT): m_res(width_px, height_px), m_pxdim(width_mm/width_px, height_mm/height_px), m_exp_time_s(exp_time), m_exp_time_first_s(exp_time_first), - m_layer_height(layer_height) + m_layer_height(layer_height), + + // Here is the trick with the orientation. + m_o(ro == RO_LANDSCAPE? Raster::Origin::BOTTOM_LEFT : + Raster::Origin::TOP_LEFT ) { } @@ -177,12 +200,12 @@ public: inline void begin_layer(unsigned lyr) { if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); - m_layers_rst[lyr].first.reset(m_res, m_pxdim, ORIGIN); + m_layers_rst[lyr].first.reset(m_res, m_pxdim, m_o); } inline void begin_layer() { m_layers_rst.emplace_back(); - m_layers_rst.front().first.reset(m_res, m_pxdim, ORIGIN); + m_layers_rst.front().first.reset(m_res, m_pxdim, m_o); } inline void finish_layer(unsigned lyr_id) { diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 3dc9451e7..8615ab91c 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -392,7 +392,7 @@ struct Pillar { void add_base(double height = 3, double radius = 2) { if(height <= 0) return; - assert(steps > 0); + assert(steps >= 0); auto last = int(steps - 1); if(radius < r ) radius = r; @@ -1293,7 +1293,7 @@ bool SLASupportTree::generate(const PointSet &points, return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y))); }); - assert(lcid > 0); + assert(lcid >= 0); auto cid = unsigned(lcid); cl_centroids.push_back(unsigned(cid)); @@ -1454,7 +1454,7 @@ bool SLASupportTree::generate(const PointSet &points, SpatIndex innerring; for(unsigned i : newring) { const Pillar& pill = result.head_pillar(gndidx[i]); - assert(pill.id > 0); + assert(pill.id >= 0); innerring.insert(pill.endpoint, unsigned(pill.id)); } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 2aed15273..72043b9da 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -413,6 +413,12 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } + +void swapXY(ExPolygon& expoly) { + for(auto& p : expoly.contour.points) std::swap(p(X), p(Y)); + for(auto& h : expoly.holes) for(auto& p : h.points) std::swap(p(X), p(Y)); +} + } void SLAPrint::process() @@ -715,7 +721,9 @@ void SLAPrint::process() std::vector keys; keys.reserve(levels.size()); for(auto& e : levels) keys.emplace_back(e.first); - bool flpXY = m_printer_config.display_flip_xy.getBool(); + // If the raster has vertical orientation, we will flip the coordinates + bool flpXY = m_printer_config.display_orientation.getInt() == + SLADisplayOrientation::sladoPortrait; { // create a raster printer for the current print parameters // I don't know any better @@ -733,7 +741,9 @@ void SLAPrint::process() if(flpXY) { std::swap(w, h); std::swap(pw, ph); } - m_printer.reset(new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t)); + m_printer.reset(new SLAPrinter(w, h, pw, ph, lh, exp_t, iexp_t, + flpXY? SLAPrinter::RO_PORTRAIT : + SLAPrinter::RO_LANDSCAPE)); } // Allocate space for all the layers @@ -770,10 +780,7 @@ void SLAPrint::process() // apply rotation before translation... slice.rotate(double(cp.rotation)); slice.translate(cp.shift(X), cp.shift(Y)); - if(flpXY) { - for(auto& p : slice.contour.points) std::swap(p(X), p(Y)); - for(auto& h : slice.holes) for(auto& p : h.points) std::swap(p(X), p(Y)); - } + if(flpXY) swapXY(slice); printer.draw_polygon(slice, level_id); } } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 4701ae20b..bf7b3ed58 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -554,6 +554,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config else if (opt_key.compare("host_type") == 0) { ret = static_cast(config.option>(opt_key)->value); } + else if (opt_key.compare("display_orientation") == 0) { + ret = static_cast(config.option>(opt_key)->value); + } } break; case coPoints: diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 4582e83ba..639f70cf7 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -453,7 +453,7 @@ const std::vector& Preset::sla_printer_options() "printer_technology", "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", - "display_flip_xy", + "display_orientation", "printer_correction", "printer_notes", "inherits" diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 66fd0e2e8..6372f7321 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1883,7 +1883,7 @@ void TabPrinter::build_sla() line.append_option(option); line.append_option(optgroup->get_option("display_pixels_y")); optgroup->append_line(line); - optgroup->append_single_option_line("display_flip_xy"); + optgroup->append_single_option_line("display_orientation"); optgroup = page->new_optgroup(_(L("Corrections"))); line = Line{ m_config->def()->get("printer_correction")->full_label, "" }; From 2c8bc7283ed0d8db16b62b2091e2bc9dbe123abe Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Dec 2018 13:05:47 +0100 Subject: [PATCH 56/61] Scroll the Object List to selected item been visible in the list (after object/part selection from the 3DScene) --- src/slic3r/GUI/GUI_ObjectList.cpp | 8 ++++++-- src/slic3r/GUI/GUI_ObjectList.hpp | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7ed6e5a28..b604e83f2 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -512,8 +512,6 @@ void ObjectList::OnDrop(wxDataViewEvent &event) parts_changed(m_selected_object_id); m_dragged_data.clear(); - -// m_prevent_list_events = false; } @@ -1453,6 +1451,12 @@ void ObjectList::update_selections() } select_items(sels); + + if (GetSelection()) { + const int sel_item_row = GetRowByItem(GetSelection()); + ScrollLines(sel_item_row - m_selected_row); + m_selected_row = sel_item_row; + } } void ObjectList::update_selections_on_canvas() diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 3664e6fda..7631782df 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -108,6 +108,8 @@ class ObjectList : public wxDataViewCtrl bool m_parts_changed = false; bool m_part_settings_changed = false; + int m_selected_row = 0; + public: ObjectList(wxWindow* parent); ~ObjectList(); From 27f5df5fbdb7056f4c260afc685c754eeb317140 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Dec 2018 13:19:11 +0100 Subject: [PATCH 57/61] Fixed broken DnD on GTK + show "Object menu" for each FullInstance instead of FullObject --- src/slic3r/GUI/GUI_ObjectList.cpp | 29 ++++++++++++++++++----------- src/slic3r/GUI/Plater.cpp | 3 ++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b604e83f2..8675a5d12 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -451,6 +451,8 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) return; } + m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), m_objects_model->GetVolumeIdByItem(item)); + /* Under MSW or OSX, DnD moves an item to the place of another selected item * But under GTK, DnD moves an item between another two items. * And as a result - call EVT_CHANGE_SELECTION to unselect all items. @@ -458,9 +460,13 @@ void ObjectList::OnBeginDrag(wxDataViewEvent &event) **/ m_prevent_list_events = true;//it's needed for GTK - m_dragged_data.init(m_objects_model->GetObjectIdByItem(item), m_objects_model->GetVolumeIdByItem(item)); + /* Under GTK, DnD requires to the wxTextDataObject been initialized with some valid value, + * so set some nonempty string + */ + wxTextDataObject* obj = new wxTextDataObject; + obj->SetText("Some text");//it's needed for GTK - event.SetDataObject(new wxTextDataObject); + event.SetDataObject(obj); event.SetDragFlags(wxDrag_DefaultMove); // allows both copy and move; } @@ -491,15 +497,16 @@ void ObjectList::OnDrop(wxDataViewEvent &event) const int from_volume_id = m_dragged_data.vol_idx(); int to_volume_id = m_objects_model->GetVolumeIdByItem(item); -#ifdef __WXGTK__ - /* Under GTK, DnD moves an item between another two items. - * And event.GetItem() return item, which is under "insertion line" - * So, if we move item down we should to decrease the to_volume_id value - **/ - if (to_volume_id > from_volume_id) to_volume_id--; -#endif // __WXGTK__ +// It looks like a fixed in current version of the wxWidgets +// #ifdef __WXGTK__ +// /* Under GTK, DnD moves an item between another two items. +// * And event.GetItem() return item, which is under "insertion line" +// * So, if we move item down we should to decrease the to_volume_id value +// **/ +// if (to_volume_id > from_volume_id) to_volume_id--; +// #endif // __WXGTK__ - auto& volumes = (*m_objects)[m_selected_object_id]->volumes; + auto& volumes = (*m_objects)[/*m_selected_object_id*/m_dragged_data.obj_idx()]->volumes; auto delta = to_volume_id < from_volume_id ? -1 : 1; int cnt = 0; for (int id = from_volume_id; cnt < abs(from_volume_id - to_volume_id); id += delta, cnt++) @@ -509,7 +516,7 @@ void ObjectList::OnDrop(wxDataViewEvent &event) m_objects_model->GetParent(item))); m_parts_changed = true; - parts_changed(m_selected_object_id); + parts_changed(/*m_selected_object_id*/m_dragged_data.obj_idx()); m_dragged_data.clear(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 67ea8c717..b1526fdb4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2473,7 +2473,8 @@ void Plater::priv::on_right_click(Vec2dEvent& evt) return; wxMenu* menu = printer_technology == ptSLA ? &sla_object_menu : - get_selection().is_single_full_object() ? &object_menu : &part_menu; + get_selection().is_single_full_instance/*object*/() ? // show "Object menu" for each FullInstance instead of FullObject + &object_menu : &part_menu; sidebar->obj_list()->append_menu_item_settings(menu); From ef04e558a71acc45bcd90bda111fec8de9f64389 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Thu, 13 Dec 2018 15:06:34 +0100 Subject: [PATCH 58/61] Temporary fix of the build --- src/slic3r/GUI/GUI_ObjectList.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 8675a5d12..575444c4b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1459,11 +1459,13 @@ void ObjectList::update_selections() select_items(sels); +#ifdef __WXMSW__ if (GetSelection()) { const int sel_item_row = GetRowByItem(GetSelection()); ScrollLines(sel_item_row - m_selected_row); m_selected_row = sel_item_row; } +#endif //__WXMSW__ } void ObjectList::update_selections_on_canvas() From dd3c4859653349a4291d287a73c2efc4ab25ff67 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Dec 2018 15:33:39 +0100 Subject: [PATCH 59/61] Some refactoring --- src/libslic3r/SLAPrint.cpp | 74 +++++++++++++++++--------------------- src/libslic3r/SLAPrint.hpp | 4 +-- 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 72043b9da..328dc5483 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -19,7 +19,6 @@ namespace Slic3r { -using SlicedModel = SlicedSupports; using SupportTreePtr = std::unique_ptr; class SLAPrintObject::SupportData { @@ -510,8 +509,6 @@ void SLAPrint::process() return; } - auto& emesh = po.m_supportdata->emesh; - auto& pts = po.m_supportdata->support_points; // nowhere filled yet try { sla::SupportConfig scfg = make_support_cfg(po.m_config); sla::Controller ctl; @@ -534,7 +531,8 @@ void SLAPrint::process() ctl.cancelfn = [this]() { throw_if_canceled(); }; po.m_supportdata->support_tree_ptr.reset( - new SLASupportTree(pts, emesh, scfg, ctl)); + new SLASupportTree(po.m_supportdata->support_points, + po.m_supportdata->emesh, scfg, ctl)); // Create the unified mesh auto rc = SlicingStatus::RELOAD_SCENE; @@ -581,12 +579,6 @@ void SLAPrint::process() po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); } -// // if the base pool (which means also the support tree) is -// // done, do a refresh when indicating progress. Now the -// // geometries for the supports and the optional base pad are -// // ready. We can grant access for the control thread to read -// // the geometries, but first we have to update the caches: -// po.support_mesh(); /*po->pad_mesh();*/ po.throw_if_canceled(); auto rc = SlicingStatus::RELOAD_SCENE; set_status(-1, L("Visualizing supports"), rc); @@ -609,8 +601,7 @@ void SLAPrint::process() po.m_slice_index.clear(); auto sih = LevelID(scale_(ilhd)); - // For all print objects, go through its initial layers and place them - // into the layers hash + // Establish the slice grid boundaries auto bb = po.transformed_mesh().bounding_box(); double modelgnd = bb.min(Z); double elevation = po.get_elevation(); @@ -626,45 +617,42 @@ void SLAPrint::process() // It is important that the next levels match the levels in // model_slice method. Only difference is that here it works with // scaled coordinates - auto& levelids = po.m_level_ids; levelids.clear(); - if(sminZ >= smodelgnd) levelids.emplace_back(sminZ); + po.m_level_ids.clear(); + if(sminZ >= smodelgnd) po.m_level_ids.emplace_back(sminZ); for(LevelID h = sminZ + sih; h < smaxZ; h += slh) - if(h >= smodelgnd) levelids.emplace_back(h); + if(h >= smodelgnd) po.m_level_ids.emplace_back(h); - SlicedModel & oslices = po.m_model_slices; + std::vector& oslices = po.m_model_slices; // If everything went well this code should not run at all, but // let's be robust... // assert(levelids.size() == oslices.size()); - if(levelids.size() < oslices.size()) { // extend the levels until... + if(po.m_level_ids.size() < oslices.size()) { // extend the levels until... BOOST_LOG_TRIVIAL(warning) << "Height level mismatch at rasterization!\n"; - LevelID lastlvl = levelids.back(); - while(levelids.size() < oslices.size()) { + LevelID lastlvl = po.m_level_ids.back(); + while(po.m_level_ids.size() < oslices.size()) { lastlvl += slh; - levelids.emplace_back(lastlvl); + po.m_level_ids.emplace_back(lastlvl); } } - // shortcut for empty index into the slice vectors - static const auto EMPTY_SLICE = SLAPrintObject::SliceRecord::NONE; - for(size_t i = 0; i < oslices.size(); ++i) { - LevelID h = levelids[i]; + LevelID h = po.m_level_ids[i]; float fh = float(double(h) * SCALING_FACTOR); // now for the public slice index: SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh]; // There should be only one slice layer for each print object - assert(sr.model_slices_idx == EMPTY_SLICE); + assert(sr.model_slices_idx == SLAPrintObject::SliceRecord::NONE); sr.model_slices_idx = i; } if(po.m_supportdata) { // deal with the support slices if present - auto& sslices = po.m_supportdata->support_slices; + std::vector& sslices = po.m_supportdata->support_slices; po.m_supportdata->level_ids.clear(); po.m_supportdata->level_ids.reserve(sslices.size()); @@ -677,16 +665,14 @@ void SLAPrint::process() float fh = float(double(h) * SCALING_FACTOR); SLAPrintObject::SliceRecord& sr = po.m_slice_index[fh]; - assert(sr.support_slices_idx == EMPTY_SLICE); + assert(sr.support_slices_idx == SLAPrintObject::SliceRecord::NONE); sr.support_slices_idx = SLAPrintObject::SliceRecord::Idx(i); } } }; - auto& levels = m_printer_input; - // Rasterizing the model objects, and their supports - auto rasterize = [this, max_objstatus, &levels]() { + auto rasterize = [this, max_objstatus]() { if(canceled()) return; // clear the rasterizer input @@ -694,32 +680,35 @@ void SLAPrint::process() for(SLAPrintObject * o : m_objects) { auto& po = *o; - SlicedModel & oslices = po.m_model_slices; + std::vector& oslices = po.m_model_slices; // We need to adjust the min Z level of the slices to be zero - LevelID smfirst = po.m_supportdata? po.m_supportdata->level_ids.front() : 0; - LevelID mfirst = po.m_level_ids.front(); + LevelID smfirst = + po.m_supportdata && !po.m_supportdata->level_ids.empty() ? + po.m_supportdata->level_ids.front() : 0; + LevelID mfirst = po.m_level_ids.empty()? 0 : po.m_level_ids.front(); LevelID gndlvl = -(std::min(smfirst, mfirst)); // now merge this object's support and object slices with the rest // of the print object slices for(size_t i = 0; i < oslices.size(); ++i) { - auto& lyrs = levels[gndlvl + po.m_level_ids[i]]; + auto& lyrs = m_printer_input[gndlvl + po.m_level_ids[i]]; lyrs.emplace_back(oslices[i], po.m_instances); } if(!po.m_supportdata) continue; - auto& sslices = po.m_supportdata->support_slices; + std::vector& sslices = po.m_supportdata->support_slices; for(size_t i = 0; i < sslices.size(); ++i) { - auto& lyrs = levels[gndlvl + po.m_supportdata->level_ids[i]]; + LayerRefs& lyrs = + m_printer_input[gndlvl + po.m_supportdata->level_ids[i]]; lyrs.emplace_back(sslices[i], po.m_instances); } } // collect all the keys - std::vector keys; keys.reserve(levels.size()); - for(auto& e : levels) keys.emplace_back(e.first); + std::vector keys; keys.reserve(m_printer_input.size()); + for(auto& e : m_printer_input) keys.emplace_back(e.first); // If the raster has vertical orientation, we will flip the coordinates bool flpXY = m_printer_config.display_orientation.getInt() == @@ -748,7 +737,7 @@ void SLAPrint::process() // Allocate space for all the layers SLAPrinter& printer = *m_printer; - auto lvlcnt = unsigned(levels.size()); + auto lvlcnt = unsigned(m_printer_input.size()); printer.layers(lvlcnt); unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; @@ -758,12 +747,12 @@ void SLAPrint::process() // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &keys, &levels, &printer, slot, sd, ist, &pst, flpXY] + [this, &slck, &keys, &printer, slot, sd, ist, &pst, flpXY] (unsigned level_id) { if(canceled()) return; - LayerRefs& lrange = levels[keys[level_id]]; + LayerRefs& lrange = m_printer_input[keys[level_id]]; // Switch to the appropriate layer in the printer printer.begin_layer(level_id); @@ -790,7 +779,7 @@ void SLAPrint::process() printer.finish_layer(level_id); // Status indication - auto st = ist + unsigned(sd*level_id*slot/levels.size()); + auto st = ist + unsigned(sd*level_id*slot/m_printer_input.size()); { std::lock_guard lck(slck); if( st > pst) { set_status(int(st), PRINT_STEP_LABELS[slapsRasterize]); @@ -911,6 +900,7 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vectorclear(); } + virtual ~SLAPrint() override { this->clear(); } - PrinterTechnology technology() const noexcept { return ptSLA; } + PrinterTechnology technology() const noexcept override { return ptSLA; } void clear() override; bool empty() const override { return m_objects.empty(); } From a16e4191380cea054e03c81732af9d0c4be269ec Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 13 Dec 2018 18:49:08 +0100 Subject: [PATCH 60/61] Fix for SPE-688 (crash when saving zip to low disk space) --- src/libslic3r/PrintExport.hpp | 14 ++++++++++---- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 13 ++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index 8187d7bf0..5cfb55217 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -227,29 +227,35 @@ public: inline void save(const std::string& path) { try { LayerWriter writer(path); + if(!writer.is_ok()) return; std::string project = writer.get_name(); writer.next_entry("config.ini"); + if(!writer.is_ok()) return; + writer << createIniContent(project); - for(unsigned i = 0; i < m_layers_rst.size(); i++) { + for(unsigned i = 0; i < m_layers_rst.size() && writer.is_ok(); i++) + { if(m_layers_rst[i].second.rdbuf()->in_avail() > 0) { char lyrnum[6]; std::sprintf(lyrnum, "%.5d", i); auto zfilename = project + lyrnum + ".png"; writer.next_entry(zfilename); + + if(!writer.is_ok()) break; + writer << m_layers_rst[i].second.str(); // writer << m_layers_rst[i].second.rdbuf(); // we can keep the date for later calls of this method //m_layers_rst[i].second.str(""); } } - - writer.close(); } catch(std::exception& e) { BOOST_LOG_TRIVIAL(error) << e.what(); - return; + // Rethrow the exception + throw; } } diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index b9f146013..891e5f0dc 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -113,10 +113,17 @@ public: zipstream(zipfile), pngstream(zipstream) { - if(!zipfile.IsOk()) + if(!is_ok()) throw std::runtime_error("Cannot create zip file."); } + ~LayerWriter() { + // In case of an error (disk space full) zipstream destructor would + // crash. + pngstream.clear(); + zipstream.CloseEntry(); + } + inline void next_entry(const std::string& fname) { zipstream.PutNextEntry(fname); } @@ -129,6 +136,10 @@ public: pngstream << arg; return *this; } + bool is_ok() const { + return pngstream.good() && zipstream.IsOk() && zipfile.IsOk(); + } + inline void close() { zipstream.Close(); zipfile.Close(); From f3e238073babb1c3a449bd18d64a5c4500414490 Mon Sep 17 00:00:00 2001 From: Vojtech Kral Date: Thu, 13 Dec 2018 18:51:08 +0100 Subject: [PATCH 61/61] Mac OS build: Set a default SDK target, fix c/cxx flags in deps/boost --- CMakeLists.txt | 7 +++++++ deps/deps-macos.cmake | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 44b77de48..625ebb334 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,13 @@ if(WIN32) endif() endif() +if (APPLE) + if (NOT CMAKE_OSX_DEPLOYMENT_TARGET) + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "OS X Deployment target (SDK version)" FORCE) + endif () + message(STATUS "Mac OS deployment target (SDK version): ${CMAKE_OSX_DEPLOYMENT_TARGET}") +endif () + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") # Workaround for an old CMake, which does not understand CMAKE_CXX_STANDARD. add_compile_options(-std=c++11 -Wall -Wno-reorder) diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index 10c15dace..720ec50d0 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -23,8 +23,8 @@ ExternalProject_Add(dep_boost variant=release threading=multi boost.locale.icu=off - "cflags=cflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}" - "cxxflags=cxxflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}" + "cflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}" + "cxxflags=-fPIC -mmacosx-version-min=${DEPS_OSX_TARGET}" install INSTALL_COMMAND "" # b2 does that already )