Merge remote-tracking branch 'origin/master' into ys_search

This commit is contained in:
YuSanka 2020-04-29 14:56:31 +02:00
commit 99d49a74d0
151 changed files with 21552 additions and 3527 deletions

View file

@ -259,7 +259,8 @@ Point Bed3D::point_projection(const Point& point) const
return m_polygon.point_projection(point);
}
void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) const
void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor,
bool show_axes, bool show_texture) const
{
m_scale_factor = scale_factor;
@ -270,9 +271,9 @@ void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool sho
switch (m_type)
{
case System: { render_system(canvas, bottom); break; }
case System: { render_system(canvas, bottom, show_texture); break; }
default:
case Custom: { render_custom(canvas, bottom); break; }
case Custom: { render_custom(canvas, bottom, show_texture); break; }
}
glsafe(::glDisable(GL_DEPTH_TEST));
@ -384,12 +385,13 @@ void Bed3D::render_axes() const
m_axes.render();
}
void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const
void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) const
{
if (!bottom)
render_model();
render_texture(bottom, canvas);
if (show_texture)
render_texture(bottom, canvas);
}
void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
@ -564,7 +566,7 @@ void Bed3D::render_model() const
}
}
void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const
void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) const
{
if (m_texture_filename.empty() && m_model_filename.empty())
{
@ -575,7 +577,8 @@ void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const
if (!bottom)
render_model();
render_texture(bottom, canvas);
if (show_texture)
render_texture(bottom, canvas);
}
void Bed3D::render_default(bool bottom) const

View file

@ -103,11 +103,15 @@ public:
// Return true if the bed shape changed, so the calee will update the UI.
bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; }
const BoundingBoxf3& get_bounding_box(bool extended) const {
return extended ? m_extended_bounding_box : m_bounding_box;
}
bool contains(const Point& point) const;
Point point_projection(const Point& point) const;
void render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) const;
void render(GLCanvas3D& canvas, bool bottom, float scale_factor,
bool show_axes, bool show_texture) const;
private:
void calc_bounding_boxes() const;
@ -115,10 +119,10 @@ private:
void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
std::tuple<EType, std::string, std::string> detect_type(const Pointfs& shape) const;
void render_axes() const;
void render_system(GLCanvas3D& canvas, bool bottom) const;
void render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) const;
void render_texture(bool bottom, GLCanvas3D& canvas) const;
void render_model() const;
void render_custom(GLCanvas3D& canvas, bool bottom) const;
void render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) const;
void render_default(bool bottom) const;
void reset();
};

View file

@ -69,6 +69,9 @@ void AppConfig::set_defaults()
set("use_retina_opengl", "1");
#endif
if (get("single_instance").empty())
set("single_instance", "0");
if (get("remember_output_path").empty())
set("remember_output_path", "1");

View file

@ -19,6 +19,7 @@
#include "libslic3r/Utils.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/libslic3r.h"
#include <cassert>
@ -149,7 +150,7 @@ void BackgroundSlicingProcess::process_sla()
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
Zipper zipper(export_path);
m_sla_print->export_raster(zipper);
m_sla_archive.export_print(zipper, *m_sla_print);
if (m_thumbnail_cb != nullptr)
{
@ -473,9 +474,9 @@ void BackgroundSlicingProcess::prepare_upload()
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
} else {
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
Zipper zipper{source_path.string()};
m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string());
if (m_thumbnail_cb != nullptr)
{
ThumbnailsList thumbnails;

View file

@ -10,6 +10,7 @@
#include <wx/event.h>
#include "libslic3r/Print.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/Utils/PrintHost.hpp"
@ -19,6 +20,7 @@ class DynamicPrintConfig;
class GCodePreviewData;
class Model;
class SLAPrint;
class SL1Archive;
class SlicingStatusEvent : public wxEvent
{
@ -47,7 +49,7 @@ public:
~BackgroundSlicingProcess();
void set_fff_print(Print *print) { m_fff_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
@ -155,6 +157,7 @@ private:
GCodePreviewData *m_gcode_preview_data = nullptr;
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
SL1Archive m_sla_archive;
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
std::string m_temp_output_path;
// Output path provided by the user. The output path may be set even if the slicing is running,

View file

@ -298,6 +298,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field("support_material_extruder", have_support_material || have_skirt);
toggle_field("support_material_speed", have_support_material || have_brim || have_skirt);
bool has_ironing = config->opt_bool("ironing");
for (auto el : { "ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed" })
toggle_field(el, has_ironing);
bool have_sequential_printing = config->opt_bool("complete_objects");
for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" })
toggle_field(el, have_sequential_printing);

View file

@ -2096,7 +2096,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->add_page(p->page_filaments = new PageMaterials(this, &p->filaments,
_(L("Filament Profiles Selection")), _(L("Filaments")), _(L("Type:")) ));
p->add_page(p->page_sla_materials = new PageMaterials(this, &p->sla_materials,
_(L("SLA Material Profiles Selection")) + " ", _(L("SLA Materials")), _(L("Layer height:")) ));
_(L("SLA Material Profiles Selection")) + " ", _(L("SLA Materials")), _(L("Type:")) ));
p->add_page(p->page_update = new PageUpdate(this));

View file

@ -945,7 +945,7 @@ void Choice::set_value(const boost::any& value, bool change_event)
}
case coEnum: {
int val = boost::any_cast<int>(value);
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern")
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
{
if (!m_opt.enum_values.empty()) {
std::string key;
@ -1015,7 +1015,7 @@ boost::any& Choice::get_value()
if (m_opt.type == coEnum)
{
int ret_enum = field->GetSelection();
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern")
if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
{
if (!m_opt.enum_values.empty()) {
std::string key = m_opt.enum_values[ret_enum];
@ -1027,8 +1027,8 @@ boost::any& Choice::get_value()
else
m_value = static_cast<InfillPattern>(0);
}
if (m_opt_id.compare("fill_pattern") == 0)
m_value = static_cast<InfillPattern>(ret_enum);
else if (m_opt_id.compare("ironing_type") == 0)
m_value = static_cast<IroningType>(ret_enum);
else if (m_opt_id.compare("gcode_flavor") == 0)
m_value = static_cast<GCodeFlavor>(ret_enum);
else if (m_opt_id.compare("support_material_pattern") == 0)

View file

@ -1745,6 +1745,8 @@ void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObje
m_render_sla_auxiliaries = visible;
for (GLVolume* vol : m_volumes.volumes) {
if (vol->composite_id.object_id == 1000)
continue; // the wipe tower
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& vol->composite_id.volume_id < 0)
@ -5565,14 +5567,19 @@ void GLCanvas3D::_render_background() const
glsafe(::glPopMatrix());
}
void GLCanvas3D::_render_bed(float theta, bool show_axes) const
void GLCanvas3D::_render_bed(bool bottom, bool show_axes) const
{
float scale_factor = 1.0;
#if ENABLE_RETINA_GL
scale_factor = m_retina_helper->get_scale_factor();
#endif // ENABLE_RETINA_GL
bool show_texture = ! bottom ||
(m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports);
#if ENABLE_NON_STATIC_CANVAS_MANAGER
wxGetApp().plater()->get_bed().render(const_cast<GLCanvas3D&>(*this), theta, scale_factor, show_axes);
wxGetApp().plater()->get_bed().render(const_cast<GLCanvas3D&>(*this), bottom, scale_factor, show_axes, show_texture);
#else
m_bed.render(const_cast<GLCanvas3D&>(*this), theta, scale_factor, show_axes);
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER

View file

@ -760,7 +760,7 @@ private:
void _picking_pass() const;
void _rectangular_selection_picking_pass() const;
void _render_background() const;
void _render_bed(float theta, bool show_axes) const;
void _render_bed(bool bottom, bool show_axes) const;
void _render_objects() const;
void _render_selection() const;
#if ENABLE_RENDER_SELECTION_CENTER

View file

@ -188,6 +188,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
opt_key == "bottom_fill_pattern" ||
opt_key == "fill_pattern")
config.set_key_value(opt_key, new ConfigOptionEnum<InfillPattern>(boost::any_cast<InfillPattern>(value)));
else if (opt_key.compare("ironing_type") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<IroningType>(boost::any_cast<IroningType>(value)));
else if (opt_key.compare("gcode_flavor") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
else if (opt_key.compare("support_material_pattern") == 0)

View file

@ -24,6 +24,7 @@
#include <wx/filefn.h>
#include <wx/sysopt.h>
#include <wx/msgdlg.h>
#include <wx/richmsgdlg.h>
#include <wx/log.h>
#include <wx/intl.h>
@ -49,6 +50,7 @@
#include "UpdateDialogs.hpp"
#include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#ifdef __WXMSW__
#include <dbt.h>
@ -208,6 +210,17 @@ static void register_win32_device_notification_event()
}
return false;
});
wxWindow::MSWRegisterMessageHandler(WM_COPYDATA, [](wxWindow* win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) {
COPYDATASTRUCT* copy_data_structure = { 0 };
copy_data_structure = (COPYDATASTRUCT*)lParam;
if (copy_data_structure->dwData == 1) {
LPCWSTR arguments = (LPCWSTR)copy_data_structure->lpData;
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(boost::nowide::narrow(arguments));
}
return true;
});
}
#endif // WIN32
@ -252,7 +265,11 @@ GUI_App::GUI_App()
, m_imgui(new ImGuiWrapper())
, m_wizard(nullptr)
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
{}
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{
//app config initializes early becasuse it is used in instance checking in PrusaSlicer.cpp
this->init_app_config();
}
GUI_App::~GUI_App()
{
@ -283,6 +300,30 @@ bool GUI_App::init_opengl()
}
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
void GUI_App::init_app_config()
{
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
SetAppName(SLIC3R_APP_KEY);
//SetAppName(SLIC3R_APP_KEY "-beta");
SetAppDisplayName(SLIC3R_APP_NAME);
// Set the Slic3r data directory at the Slic3r XS module.
// Unix: ~/ .Slic3r
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
// Mac : "~/Library/Application Support/Slic3r"
if (data_dir().empty())
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
if (!app_config)
app_config = new AppConfig();
// load settings
app_conf_exists = app_config->exists();
if (app_conf_exists) {
app_config->load();
}
}
bool GUI_App::OnInit()
{
try {
@ -300,42 +341,41 @@ bool GUI_App::on_init_inner()
wxCHECK_MSG(wxDirExists(resources_dir), false,
wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir));
// Profiles for the alpha are stored into the PrusaSlicer-alpha directory to not mix with the current release.
SetAppName(SLIC3R_APP_KEY);
// SetAppName(SLIC3R_APP_KEY "-beta");
SetAppDisplayName(SLIC3R_APP_NAME);
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
// Enable this to get the default Win32 COMCTRL32 behavior of static boxes.
// wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0);
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
// performance when working on high resolution multi-display setups.
// Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible
// performance when working on high resolution multi-display setups.
// wxSystemOptions::SetOption("msw.notebook.themed-background", 0);
// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
std::string msg = Http::tls_global_init();
std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location");
bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store();
if (!msg.empty() && !ssl_accept) {
wxRichMessageDialog
dlg(nullptr,
wxString::Format(_(L("%s\nDo you want to continue?")), msg),
"PrusaSlicer", wxICON_QUESTION | wxYES_NO);
dlg.ShowCheckBox(_(L("Remember my choice")));
if (dlg.ShowModal() != wxID_YES) return false;
// Set the Slic3r data directory at the Slic3r XS module.
// Unix: ~/ .Slic3r
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
// Mac : "~/Library/Application Support/Slic3r"
if (data_dir().empty())
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
app_config = new AppConfig();
app_config->set("tls_cert_store_accepted",
dlg.IsCheckBoxChecked() ? "yes" : "no");
app_config->set("tls_accepted_cert_store_location",
dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : "");
}
app_config->set("version", SLIC3R_VERSION);
app_config->save();
preset_bundle = new PresetBundle();
// just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
// supplied as argument to --datadir; in that case we should still run the wizard
preset_bundle->setup_directories();
// load settings
app_conf_exists = app_config->exists();
if (app_conf_exists) {
app_config->load();
}
app_config->set("version", SLIC3R_VERSION);
app_config->save();
#ifdef __WXMSW__
associate_3mf_files();
#endif // __WXMSW__
@ -387,6 +427,8 @@ bool GUI_App::on_init_inner()
if (! plater_)
return;
//m_other_instance_message_handler->report();
if (app_config->dirty() && app_config->get("autosave") == "1")
app_config->save();

View file

@ -35,6 +35,7 @@ class PrintHostJobQueue;
namespace GUI{
class RemovableDriveManager;
class OtherInstanceMessageHandler;
enum FileType
{
FT_STL,
@ -108,7 +109,7 @@ class GUI_App : public wxApp
std::unique_ptr<ImGuiWrapper> m_imgui;
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
ConfigWizard* m_wizard; // Managed by wxWindow tree
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
public:
bool OnInit() override;
bool initialized() const { return m_initialized; }
@ -196,6 +197,7 @@ public:
std::vector<Tab *> tabs_list;
RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); }
OtherInstanceMessageHandler* other_instance_message_handler() { return m_other_instance_message_handler.get(); }
ImGuiWrapper* imgui() { return m_imgui.get(); }
@ -211,6 +213,7 @@ public:
private:
bool on_init_inner();
void init_app_config();
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
void window_pos_sanitize(wxTopLevelWindow* window);

View file

@ -1209,6 +1209,13 @@ static bool improper_category(const std::string& category, const int extruders_c
(!is_object_settings && category == "Support material");
}
static bool is_object_item(ItemType item_type)
{
return item_type & itObject || item_type & itInstance ||
// multi-selection in ObjectList, but full_object in Selection
(item_type == itUndef && scene_selection().is_single_full_object());
}
void ObjectList::get_options_menu(settings_menu_hierarchy& settings_menu, const bool is_part)
{
auto options = get_options(is_part);
@ -1579,9 +1586,7 @@ wxMenuItem* ObjectList::append_menu_item_settings(wxMenu* menu_)
const ItemType item_type = m_objects_model->GetItemType(GetSelection());
if (item_type == itUndef && !selection.is_single_full_object())
return nullptr;
const bool is_object_settings = item_type & itObject || item_type & itInstance ||
// multi-selection in ObjectList, but full_object in Selection
(item_type == itUndef && selection.is_single_full_object());
const bool is_object_settings = is_object_item(item_type);
create_freq_settings_popupmenu(menu, is_object_settings);
if (mode == comAdvanced)
@ -1821,8 +1826,7 @@ wxMenu* ObjectList::create_settings_popupmenu(wxMenu *parent_menu)
const wxDataViewItem selected_item = GetSelection();
wxDataViewItem item = m_objects_model->GetItemType(selected_item) & itSettings ? m_objects_model->GetParent(selected_item) : selected_item;
const bool is_part = !(m_objects_model->GetItemType(item) == itObject || scene_selection().is_single_full_object());
get_options_menu(settings_menu, is_part);
get_options_menu(settings_menu, !is_object_item(m_objects_model->GetItemType(item)));
for (auto cat : settings_menu) {
append_menu_item(menu, wxID_ANY, _(cat.first), "",
@ -2067,37 +2071,40 @@ void ObjectList::load_shape_object(const std::string& type_name)
// Create mesh
BoundingBoxf3 bb;
TriangleMesh mesh = create_mesh(type_name, bb);
load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name));
}
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name)
{
// Add mesh to model as a new object
Model& model = wxGetApp().plater()->model();
const wxString name = _(L("Shape")) + "-" + _(type_name);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
std::vector<size_t> object_idxs;
ModelObject* new_object = model.add_object();
new_object->name = into_u8(name);
new_object->add_instance(); // each object should have at list one instance
ModelVolume* new_volume = new_object->add_volume(mesh);
new_volume->name = into_u8(name);
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_object->invalidate_bounding_box();
new_object->center_around_origin();
new_object->ensure_on_bed();
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
object_idxs.push_back(model.objects.size() - 1);
#ifdef _DEBUG
check_model_ids_validity(model);
#endif /* _DEBUG */
paste_objects_into_list(object_idxs);
#ifdef _DEBUG

View file

@ -24,6 +24,7 @@ class ConfigOptionsGroup;
class DynamicPrintConfig;
class ModelObject;
class ModelVolume;
class TriangleMesh;
enum class ModelVolumeType : int;
// FIXME: broken build on mac os because of this is missing:
@ -265,6 +266,7 @@ public:
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_shape_object(const std::string &type_name);
void load_mesh_object(const TriangleMesh &mesh, const wxString &name);
void del_object(const int obj_idx);
void del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item);

View file

@ -8,6 +8,7 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "libslic3r/Model.hpp"
@ -45,6 +46,7 @@ bool GLGizmoFdmSupports::on_init()
m_desc["block"] = _L("Block supports");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove selection");
m_desc["remove_all"] = _L("Remove all");
return true;
}
@ -101,10 +103,14 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
glsafe(::glColor4f(0.2f, 0.2f, 1.0f, 0.5f));
m_ivas[mesh_id][0].render();
glsafe(::glColor4f(1.f, 0.2f, 0.2f, 0.5f));
m_ivas[mesh_id][1].render();
// Now render both enforcers and blockers.
for (int i=0; i<2; ++i) {
if (m_ivas[mesh_id][i].has_VBOs()) {
glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f));
m_ivas[mesh_id][i].render();
}
}
glsafe(::glPopMatrix());
}
}
@ -187,7 +193,16 @@ void GLGizmoFdmSupports::update_mesh()
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_selected_facets[volume_id].assign(mesh->its.indices.size(), SelType::NONE);
m_selected_facets[volume_id].assign(mesh->its.indices.size(), FacetSupportType::NONE);
// Load current state from ModelVolume.
for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) {
const std::vector<int>& list = mv->m_supported_facets.get_facets(type);
for (int i : list)
m_selected_facets[volume_id][i] = type;
}
update_vertex_buffers(mv, volume_id, true, true);
m_neighbors[volume_id].resize(3 * mesh->its.indices.size());
// Prepare vector of vertex_index - facet_index pairs to quickly find adjacent facets
@ -243,16 +258,16 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|| action == SLAGizmoEventType::RightDown
|| (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
SelType new_state = SelType::NONE;
FacetSupportType new_state = FacetSupportType::NONE;
if (! shift_down) {
if (action == SLAGizmoEventType::Dragging)
new_state = m_button_down == Button::Left
? SelType::ENFORCER
: SelType::BLOCKER;
? FacetSupportType::ENFORCER
: FacetSupportType::BLOCKER;
else
new_state = action == SLAGizmoEventType::LeftDown
? SelType::ENFORCER
: SelType::BLOCKER;
? FacetSupportType::ENFORCER
: FacetSupportType::BLOCKER;
}
const Camera& camera = wxGetApp().plater()->get_camera();
@ -379,9 +394,9 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
// Now just select all facets that passed.
for (size_t next_facet : facets_to_select) {
SelType& facet = m_selected_facets[mesh_id][next_facet];
FacetSupportType& facet = m_selected_facets[mesh_id][next_facet];
if (facet != new_state && facet != SelType::NONE) {
if (facet != new_state && facet != FacetSupportType::NONE) {
// this triangle is currently in the other VBA.
// Both VBAs need to be refreshed.
update_both = true;
@ -391,8 +406,8 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
}
update_vertex_buffers(mv, mesh_id,
new_state == SelType::ENFORCER || update_both,
new_state == SelType::BLOCKER || update_both
new_state == FacetSupportType::ENFORCER || update_both,
new_state == FacetSupportType::BLOCKER || update_both
);
}
@ -416,6 +431,18 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
&& m_button_down != Button::None) {
m_button_down = Button::None;
// Synchronize gizmo with ModelVolume data.
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
++idx;
if (! mv->is_model_part())
continue;
for (int i=0; i<int(m_selected_facets[idx].size()); ++i)
mv->m_supported_facets.set_facet(i, m_selected_facets[idx][i]);
}
return true;
}
@ -430,16 +457,16 @@ void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv,
{
const TriangleMesh* mesh = &mv->mesh();
for (SelType type : {SelType::ENFORCER, SelType::BLOCKER}) {
if ((type == SelType::ENFORCER && ! update_enforcers)
|| (type == SelType::BLOCKER && ! update_blockers))
for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) {
if ((type == FacetSupportType::ENFORCER && ! update_enforcers)
|| (type == FacetSupportType::BLOCKER && ! update_blockers))
continue;
GLIndexedVertexArray& iva = m_ivas[mesh_id][type==SelType::ENFORCER ? 0 : 1];
GLIndexedVertexArray& iva = m_ivas[mesh_id][type==FacetSupportType::ENFORCER ? 0 : 1];
iva.release_geometry();
size_t triangle_cnt=0;
for (size_t facet_idx=0; facet_idx<m_selected_facets[mesh_id].size(); ++facet_idx) {
SelType status = m_selected_facets[mesh_id][facet_idx];
FacetSupportType status = m_selected_facets[mesh_id][facet_idx];
if (status != type)
continue;
for (int i=0; i<3; ++i)
@ -448,7 +475,8 @@ void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv,
iva.push_triangle(3*triangle_cnt, 3*triangle_cnt+1, 3*triangle_cnt+2);
++triangle_cnt;
}
iva.finalize_geometry(true);
if (! m_selected_facets[mesh_id].empty())
iva.finalize_geometry(true);
}
}
@ -466,6 +494,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
float caption_max = 0.f;
@ -479,6 +508,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
@ -494,6 +524,20 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_imgui->text("");
if (m_imgui->button(m_desc.at("remove_all"))) {
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
++idx;
if (mv->is_model_part()) {
m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE);
mv->m_supported_facets.clear();
update_vertex_buffers(mv, idx, true, true);
m_parent.set_as_dirty();
}
}
}
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
m_imgui->text(m_desc.at("cursor_size"));

View file

@ -9,6 +9,9 @@
namespace Slic3r {
enum class FacetSupportType : int8_t;
namespace GUI {
enum class SLAGizmoEventType : unsigned char;
@ -26,15 +29,9 @@ private:
static constexpr float CursorRadiusMax = 8.f;
static constexpr float CursorRadiusStep = 0.2f;
enum class SelType : int8_t {
NONE,
ENFORCER,
BLOCKER
};
// For each model-part volume, store a list of statuses of
// individual facets (one of the enum values above).
std::vector<std::vector<SelType>> m_selected_facets;
std::vector<std::vector<FacetSupportType>> m_selected_facets;
// Store two vertex buffer arrays (for enforcers/blockers)
// for each model-part volume.

View file

@ -0,0 +1,495 @@
#include "GUI_App.hpp"
#include "InstanceCheck.hpp"
#include "boost/nowide/convert.hpp"
#include <boost/log/trivial.hpp>
#include <iostream>
#include <fcntl.h>
#include <errno.h>
#if __linux__
#include <dbus/dbus.h> /* Pull in all of D-Bus headers. */
#endif //__linux__
namespace Slic3r {
namespace instance_check_internal
{
struct CommandLineAnalysis
{
bool should_send;
std::string cl_string;
};
static CommandLineAnalysis process_command_line(int argc, char** argv) //d:\3dmodels\Klapka\Klapka.3mf
{
CommandLineAnalysis ret { false };
if (argc < 2)
return ret;
ret.cl_string = argv[0];
for (size_t i = 1; i < argc; i++) {
std::string token = argv[i];
if (token == "--single-instance") {
ret.should_send = true;
} else {
ret.cl_string += " ";
ret.cl_string += token;
}
}
BOOST_LOG_TRIVIAL(debug) << "single instance: "<< ret.should_send << ". other params: " << ret.cl_string;
return ret;
}
} //namespace instance_check_internal
#if _WIN32
namespace instance_check_internal
{
static HWND l_prusa_slicer_hwnd;
static BOOL CALLBACK EnumWindowsProc(_In_ HWND hwnd, _In_ LPARAM lParam)
{
//checks for other instances of prusaslicer, if found brings it to front and return false to stop enumeration and quit this instance
//search is done by classname(wxWindowNR is wxwidgets thing, so probably not unique) and name in window upper panel
//other option would be do a mutex and check for its existence
TCHAR wndText[1000];
TCHAR className[1000];
GetClassName(hwnd, className, 1000);
GetWindowText(hwnd, wndText, 1000);
std::wstring classNameString(className);
std::wstring wndTextString(wndText);
if (wndTextString.find(L"PrusaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") {
l_prusa_slicer_hwnd = hwnd;
ShowWindow(hwnd, SW_SHOWMAXIMIZED);
SetForegroundWindow(hwnd);
return false;
}
return true;
}
static void send_message(const HWND hwnd)
{
LPWSTR command_line_args = GetCommandLine();
//Create a COPYDATASTRUCT to send the information
//cbData represents the size of the information we want to send.
//lpData represents the information we want to send.
//dwData is an ID defined by us(this is a type of ID different than WM_COPYDATA).
COPYDATASTRUCT data_to_send = { 0 };
data_to_send.dwData = 1;
data_to_send.cbData = sizeof(TCHAR) * (wcslen(command_line_args) + 1);
data_to_send.lpData = command_line_args;
SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send);
}
} //namespace instance_check_internal
bool instance_check(int argc, char** argv, bool app_config_single_instance)
{
instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv);
if (cla.should_send || app_config_single_instance) {
// Call EnumWidnows with own callback. cons: Based on text in the name of the window and class name which is generic.
if (!EnumWindows(instance_check_internal::EnumWindowsProc, 0)) {
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate.";
instance_check_internal::send_message(instance_check_internal::l_prusa_slicer_hwnd);
return true;
}
}
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set.";
return false;
}
#elif defined(__APPLE__)
namespace instance_check_internal
{
static int get_lock()
{
struct flock fl;
int fdlock;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1)
return 0;
if (fcntl(fdlock, F_SETLK, &fl) == -1)
return 0;
return 1;
}
} //namespace instance_check_internal
bool instance_check(int argc, char** argv, bool app_config_single_instance)
{
instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv);
if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) {
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate.";
send_message_mac(cla.cl_string);
return true;
}
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set.";
return false;
}
#elif defined(__linux__)
namespace instance_check_internal
{
static int get_lock()
{
struct flock fl;
int fdlock;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 1;
if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1)
return 0;
if (fcntl(fdlock, F_SETLK, &fl) == -1)
return 0;
return 1;
}
static void send_message(std::string message_text)
{
DBusMessage* msg;
DBusMessageIter args;
DBusConnection* conn;
DBusError err;
dbus_uint32_t serial = 0;
const char* sigval = message_text.c_str();
std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck";
std::string method_name = "AnotherInstace";
std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck";
// initialise the error value
dbus_error_init(&err);
// connect to bus, and check for errors (use SESSION bus everywhere!)
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error. Message to another instance wont be send.";
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: "<< err.message;
dbus_error_free(&err);
return;
}
if (NULL == conn) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Message to another instance wont be send.";
return;
}
//some sources do request interface ownership before constructing msg but i think its wrong.
//create new method call message
msg = dbus_message_new_method_call(interface_name.c_str(), object_name.c_str(), interface_name.c_str(), method_name.c_str());
if (NULL == msg) {
BOOST_LOG_TRIVIAL(error) << "DBus Message is NULL. Message to another instance wont be send.";
dbus_connection_unref(conn);
return;
}
//the AnotherInstace method is not sending reply.
dbus_message_set_no_reply(msg, TRUE);
//append arguments to message
if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &sigval, DBUS_TYPE_INVALID)) {
BOOST_LOG_TRIVIAL(error) << "Ran out of memory while constructing args for DBus message. Message to another instance wont be send.";
dbus_message_unref(msg);
dbus_connection_unref(conn);
return;
}
// send the message and flush the connection
if (!dbus_connection_send(conn, msg, &serial)) {
BOOST_LOG_TRIVIAL(error) << "Ran out of memory while sending DBus message.";
dbus_message_unref(msg);
dbus_connection_unref(conn);
return;
}
dbus_connection_flush(conn);
BOOST_LOG_TRIVIAL(trace) << "DBus message sent.";
// free the message and close the connection
dbus_message_unref(msg);
dbus_connection_unref(conn);
}
} //namespace instance_check_internal
bool instance_check(int argc, char** argv, bool app_config_single_instance)
{
instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv);
if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) {
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate.";
instance_check_internal::send_message(cla.cl_string);
return true;
}
BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set.";
return false;
}
#endif //_WIN32/__APPLE__/__linux__
namespace GUI {
wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler)
{
assert(!m_initialized);
assert(m_callback_evt_handler == nullptr);
if (m_initialized)
return;
m_initialized = true;
m_callback_evt_handler = callback_evt_handler;
#if _WIN32
//create_listener_window();
#endif //_WIN32
#if defined(__APPLE__)
this->register_for_messages();
#endif //__APPLE__
#ifdef BACKGROUND_MESSAGE_LISTENER
m_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen, this)));
#endif //BACKGROUND_MESSAGE_LISTENER
}
void OtherInstanceMessageHandler::shutdown()
{
BOOST_LOG_TRIVIAL(debug) << "message handler shutdown().";
assert(m_initialized);
if (m_initialized) {
#if __APPLE__
//delete macos implementation
this->unregister_for_messages();
#endif //__APPLE__
#ifdef BACKGROUND_MESSAGE_LISTENER
if (m_thread.joinable()) {
// Stop the worker thread, if running.
{
// Notify the worker thread to cancel wait on detection polling.
std::lock_guard<std::mutex> lck(m_thread_stop_mutex);
m_stop = true;
}
m_thread_stop_condition.notify_all();
// Wait for the worker thread to stop.
m_thread.join();
m_stop = false;
}
#endif //BACKGROUND_MESSAGE_LISTENER
m_initialized = false;
}
}
namespace MessageHandlerInternal
{
// returns ::path to possible model or empty ::path if input string is not existing path
static boost::filesystem::path get_path(const std::string possible_path)
{
BOOST_LOG_TRIVIAL(debug) << "message part: " << possible_path;
if (possible_path.empty() || possible_path.size() < 3) {
BOOST_LOG_TRIVIAL(debug) << "empty";
return boost::filesystem::path();
}
if (boost::filesystem::exists(possible_path)) {
BOOST_LOG_TRIVIAL(debug) << "is path";
return boost::filesystem::path(possible_path);
} else if (possible_path[0] == '\"') {
if(boost::filesystem::exists(possible_path.substr(1, possible_path.size() - 2))) {
BOOST_LOG_TRIVIAL(debug) << "is path in quotes";
return boost::filesystem::path(possible_path.substr(1, possible_path.size() - 2));
}
}
BOOST_LOG_TRIVIAL(debug) << "is NOT path";
return boost::filesystem::path();
}
} //namespace MessageHandlerInternal
void OtherInstanceMessageHandler::handle_message(const std::string message) {
std::vector<boost::filesystem::path> paths;
auto next_space = message.find(' ');
size_t last_space = 0;
int counter = 0;
BOOST_LOG_TRIVIAL(info) << "message from other instance: " << message;
while (next_space != std::string::npos)
{
if (counter != 0) {
const std::string possible_path = message.substr(last_space, next_space - last_space);
boost::filesystem::path p = MessageHandlerInternal::get_path(possible_path);
if(!p.string().empty())
paths.emplace_back(p);
}
last_space = next_space;
next_space = message.find(' ', last_space + 1);
counter++;
}
if (counter != 0 ) {
boost::filesystem::path p = MessageHandlerInternal::get_path(message.substr(last_space + 1));
if (!p.string().empty())
paths.emplace_back(p);
}
if (!paths.empty()) {
//wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here?
//if (evt_handler) {
wxPostEvent(m_callback_evt_handler, LoadFromOtherInstanceEvent(GUI::EVT_LOAD_MODEL_OTHER_INSTANCE, std::vector<boost::filesystem::path>(std::move(paths))));
//}
}
}
#ifdef BACKGROUND_MESSAGE_LISTENER
namespace MessageHandlerDBusInternal
{
//reply to introspect makes our DBus object visible for other programs like D-Feet
static void respond_to_introspect(DBusConnection *connection, DBusMessage *request)
{
DBusMessage *reply;
const char *introspection_data =
" <!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">"
" <!-- dbus-sharp 0.8.1 -->"
" <node>"
" <interface name=\"org.freedesktop.DBus.Introspectable\">"
" <method name=\"Introspect\">"
" <arg name=\"data\" direction=\"out\" type=\"s\" />"
" </method>"
" </interface>"
" <interface name=\"com.prusa3d.prusaslicer.InstanceCheck\">"
" <method name=\"AnotherInstace\">"
" <arg name=\"data\" direction=\"in\" type=\"s\" />"
" </method>"
" </interface>"
" </node>";
reply = dbus_message_new_method_return(request);
dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_data, DBUS_TYPE_INVALID);
dbus_connection_send(connection, reply, NULL);
dbus_message_unref(reply);
}
//method AnotherInstance receives message from another PrusaSlicer instance
static void handle_method_another_instance(DBusConnection *connection, DBusMessage *request)
{
DBusError err;
char* text= "";
wxEvtHandler* evt_handler;
dbus_error_init(&err);
dbus_message_get_args(request, &err, DBUS_TYPE_STRING, &text, DBUS_TYPE_INVALID);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(trace) << "Dbus method AnotherInstance received with wrong arguments.";
dbus_error_free(&err);
return;
}
wxGetApp().other_instance_message_handler()->handle_message(text);
evt_handler = wxGetApp().plater();
if (evt_handler) {
wxPostEvent(evt_handler, InstanceGoToFrontEvent(EVT_INSTANCE_GO_TO_FRONT));
}
}
//every dbus message received comes here
static DBusHandlerResult handle_dbus_object_message(DBusConnection *connection, DBusMessage *message, void *user_data)
{
const char* interface_name = dbus_message_get_interface(message);
const char* member_name = dbus_message_get_member(message);
BOOST_LOG_TRIVIAL(trace) << "DBus message received: interface: " << interface_name << ", member: " << member_name;
if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) {
respond_to_introspect(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
} else if (0 == strcmp("com.prusa3d.prusaslicer.InstanceCheck", interface_name) && 0 == strcmp("AnotherInstace", member_name)) {
handle_method_another_instance(connection, message);
return DBUS_HANDLER_RESULT_HANDLED;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
} //namespace MessageHandlerDBusInternal
void OtherInstanceMessageHandler::listen()
{
DBusConnection* conn;
DBusError err;
int name_req_val;
DBusObjectPathVTable vtable;
std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck";
std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck";
dbus_error_init(&err);
// connect to the bus and check for errors (use SESSION bus everywhere!)
conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: "<< err.message;
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_error_free(&err);
return;
}
if (NULL == conn) {
BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Dbus Messages listening terminating.";
return;
}
// request our name on the bus and check for errors
name_req_val = dbus_bus_request_name(conn, interface_name.c_str(), DBUS_NAME_FLAG_REPLACE_EXISTING , &err);
if (dbus_error_is_set(&err)) {
BOOST_LOG_TRIVIAL(error) << "DBus Request name Error: "<< err.message;
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_error_free(&err);
dbus_connection_unref(conn);
return;
}
if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != name_req_val) {
BOOST_LOG_TRIVIAL(error) << "Not primary owner of DBus name - probably another PrusaSlicer instance is running.";
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_connection_unref(conn);
return;
}
// Set callbacks. Unregister function should not be nessary.
vtable.message_function = MessageHandlerDBusInternal::handle_dbus_object_message;
vtable.unregister_function = NULL;
// register new object - this is our access to DBus
dbus_connection_try_register_object_path(conn, object_name.c_str(), &vtable, NULL, &err);
if ( dbus_error_is_set(&err) ) {
BOOST_LOG_TRIVIAL(error) << "DBus Register object Error: "<< err.message;
BOOST_LOG_TRIVIAL(error) << "Dbus Messages listening terminating.";
dbus_connection_unref(conn);
dbus_error_free(&err);
return;
}
BOOST_LOG_TRIVIAL(trace) << "Dbus object registered. Starting listening for messages.";
for (;;) {
// Wait for 1 second
// Cancellable.
{
std::unique_lock<std::mutex> lck(m_thread_stop_mutex);
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_stop; });
}
if (m_stop)
// Stop the worker thread.
break;
//dispatch should do all the work with incoming messages
//second parameter is blocking time that funciton waits for new messages
//that is handled here with our own event loop above
dbus_connection_read_write_dispatch(conn, 0);
}
dbus_connection_unref(conn);
}
#endif //BACKGROUND_MESSAGE_LISTENER
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,91 @@
#ifndef slic3r_InstanceCheck_hpp_
#define slic3r_InstanceCheck_hpp_
#include "Event.hpp"
#if _WIN32
#include <windows.h>
#endif //_WIN32
#include <string>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <tbb/mutex.h>
#include <condition_variable>
namespace Slic3r {
// checks for other running instances and sends them argv,
// if there is --single-instance argument or AppConfig is set to single_instance=1
// returns true if this instance should terminate
bool instance_check(int argc, char** argv, bool app_config_single_instance);
#if __APPLE__
// apple implementation of inner functions of instance_check
// in InstanceCheckMac.mm
void send_message_mac(const std::string msg);
#endif //__APPLE__
namespace GUI {
#if __linux__
#define BACKGROUND_MESSAGE_LISTENER
#endif // __linux__
using LoadFromOtherInstanceEvent = Event<std::vector<boost::filesystem::path>>;
wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
using InstanceGoToFrontEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
class OtherInstanceMessageHandler
{
public:
OtherInstanceMessageHandler() = default;
OtherInstanceMessageHandler(OtherInstanceMessageHandler const&) = delete;
void operator=(OtherInstanceMessageHandler const&) = delete;
~OtherInstanceMessageHandler() { assert(!m_initialized); }
// inits listening, on each platform different. On linux starts background thread
void init(wxEvtHandler* callback_evt_handler);
// stops listening, on linux stops the background thread
void shutdown();
//finds paths to models in message(= command line arguments, first should be prusaSlicer executable)
//and sends them to plater via LoadFromOtherInstanceEvent
//security of messages: from message all existing paths are proccesed to load model
// win32 - anybody who has hwnd can send message.
// mac - anybody who posts notification with name:@"OtherPrusaSlicerTerminating"
// linux - instrospectable on dbus
void handle_message(const std::string message);
private:
bool m_initialized { false };
wxEvtHandler* m_callback_evt_handler { nullptr };
#ifdef BACKGROUND_MESSAGE_LISTENER
//worker thread to listen incoming dbus communication
boost::thread m_thread;
std::condition_variable m_thread_stop_condition;
mutable std::mutex m_thread_stop_mutex;
bool m_stop{ false };
bool m_start{ true };
// background thread method
void listen();
#endif //BACKGROUND_MESSAGE_LISTENER
#if __APPLE__
//implemented at InstanceCheckMac.mm
void register_for_messages();
void unregister_for_messages();
// Opaque pointer to RemovableDriveManagerMM
void* m_impl_osx;
#endif //__APPLE__
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_InstanceCheck_hpp_

View file

@ -0,0 +1,8 @@
#import <Cocoa/Cocoa.h>
@interface OtherInstanceMessageHandlerMac : NSObject
-(instancetype) init;
-(void) add_observer;
-(void) message_update:(NSNotification *)note;
@end

View file

@ -0,0 +1,68 @@
#import "InstanceCheck.hpp"
#import "InstanceCheckMac.h"
#import "GUI_App.hpp"
@implementation OtherInstanceMessageHandlerMac
-(instancetype) init
{
self = [super init];
return self;
}
-(void)add_observer
{
NSLog(@"adding observer");
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_update:) name:@"OtherPrusaSlicerInstanceMessage" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately];
}
-(void)message_update:(NSNotification *)msg
{
//NSLog(@"recieved msg %@", msg);
//demiaturize all windows
for(NSWindow* win in [NSApp windows])
{
if([win isMiniaturized])
{
[win deminiaturize:self];
}
}
//bring window to front
[[NSApplication sharedApplication] activateIgnoringOtherApps : YES];
//pass message
Slic3r::GUI::wxGetApp().other_instance_message_handler()->handle_message(std::string([msg.userInfo[@"data"] UTF8String]));
}
@end
namespace Slic3r {
void send_message_mac(const std::string msg)
{
NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]];
//NSLog(@"sending msg %@", nsmsg);
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"OtherPrusaSlicerInstanceMessage" object:nil userInfo:[NSDictionary dictionaryWithObject:nsmsg forKey:@"data"] deliverImmediately:YES];
}
namespace GUI {
void OtherInstanceMessageHandler::register_for_messages()
{
m_impl_osx = [[OtherInstanceMessageHandlerMac alloc] init];
if(m_impl_osx) {
[m_impl_osx add_observer];
}
}
void OtherInstanceMessageHandler::unregister_for_messages()
{
//NSLog(@"unreegistering other instance messages");
if (m_impl_osx) {
[m_impl_osx release];
m_impl_osx = nullptr;
} else {
NSLog(@"unreegister not required");
}
}
}//namespace GUI
}//namespace Slicer

View file

@ -1,155 +0,0 @@
#ifndef JOB_HPP
#define JOB_HPP
#include <atomic>
#include <slic3r/Utils/Thread.hpp>
#include <slic3r/GUI/I18N.hpp>
#include <slic3r/GUI/ProgressIndicator.hpp>
#include <wx/event.h>
#include <boost/thread.hpp>
namespace Slic3r { namespace GUI {
// A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
// state in the status progress indicator. On the other hand they are
// separated from the background slicing process. Ideally, these jobs should
// run when the background process is not running.
//
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class Job : public wxEvtHandler
{
int m_range = 100;
boost::thread m_thread;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
std::shared_ptr<ProgressIndicator> m_progress;
void run()
{
m_running.store(true);
process();
m_running.store(false);
// ensure to call the last status to finalize the job
update_status(status_range(), "");
}
protected:
// status range for a particular job
virtual int status_range() const { return 100; }
// status update, to be used from the work thread (process() method)
void update_status(int st, const wxString &msg = "")
{
auto evt = new wxThreadEvent();
evt->SetInt(st);
evt->SetString(msg);
wxQueueEvent(this, evt);
}
bool was_canceled() const { return m_canceled.load(); }
// Launched just before start(), a job can use it to prepare internals
virtual void prepare() {}
// Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize() { m_finalized = true; }
public:
Job(std::shared_ptr<ProgressIndicator> pri) : m_progress(pri)
{
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
auto msg = evt.GetString();
if (!msg.empty())
m_progress->set_status_text(msg.ToUTF8().data());
if (m_finalized) return;
m_progress->set_progress(evt.GetInt());
if (evt.GetInt() == status_range()) {
// set back the original range and cancel callback
m_progress->set_range(m_range);
m_progress->set_cancel_callback();
wxEndBusyCursor();
finalize();
// dont do finalization again for the same process
m_finalized = true;
}
});
}
bool is_finalized() const { return m_finalized; }
Job(const Job &) = delete;
Job(Job &&) = delete;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = delete;
virtual void process() = 0;
void start()
{ // Start the job. No effect if the job is already running
if (!m_running.load()) {
prepare();
// Save the current status indicatior range and push the new one
m_range = m_progress->get_range();
m_progress->set_range(status_range());
// init cancellation flag and set the cancel callback
m_canceled.store(false);
m_progress->set_cancel_callback(
[this]() { m_canceled.store(true); });
m_finalized = false;
// Changing cursor to busy
wxBeginBusyCursor();
try { // Execute the job
m_thread = create_thread([this] { this->run(); });
} catch (std::exception &) {
update_status(status_range(),
_(L("ERROR: not enough resources to "
"execute a new job.")));
}
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
}
}
// To wait for the running job and join the threads. False is
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
bool join(int timeout_ms = 0)
{
if (!m_thread.joinable()) return true;
if (timeout_ms <= 0)
m_thread.join();
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
return false;
return true;
}
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
};
}
}
#endif // JOB_HPP

View file

@ -0,0 +1,223 @@
#include "ArrangeJob.hpp"
#include "libslic3r/MTUtils.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI.hpp"
namespace Slic3r { namespace GUI {
// Cache the wti info
class WipeTower: public GLCanvas3D::WipeTowerInfo {
using ArrangePolygon = arrangement::ArrangePolygon;
public:
explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti)
: GLCanvas3D::WipeTowerInfo(wti)
{}
explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti)
: GLCanvas3D::WipeTowerInfo(std::move(wti))
{}
void apply_arrange_result(const Vec2d& tr, double rotation)
{
m_pos = unscaled(tr); m_rotation = rotation;
apply_wipe_tower();
}
ArrangePolygon get_arrange_polygon() const
{
Polygon ap({
{coord_t(0), coord_t(0)},
{scaled(m_bb_size(X)), coord_t(0)},
{scaled(m_bb_size)},
{coord_t(0), scaled(m_bb_size(Y))},
{coord_t(0), coord_t(0)},
});
ArrangePolygon ret;
ret.poly.contour = std::move(ap);
ret.translation = scaled(m_pos);
ret.rotation = m_rotation;
ret.priority++;
return ret;
}
};
static WipeTower get_wipe_tower(Plater &plater)
{
return WipeTower{plater.canvas3D()->get_wipe_tower_info()};
}
void ArrangeJob::clear_input()
{
const Model &model = m_plater->model();
size_t count = 0, cunprint = 0; // To know how much space to reserve
for (auto obj : model.objects)
for (auto mi : obj->instances)
mi->printable ? count++ : cunprint++;
m_selected.clear();
m_unselected.clear();
m_unprintable.clear();
m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */);
m_unprintable.reserve(cunprint /* for optional wti */);
}
double ArrangeJob::bed_stride() const {
double bedwidth = m_plater->bed_shape_bb().size().x();
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
}
void ArrangeJob::prepare_all() {
clear_input();
for (ModelObject *obj: m_plater->model().objects)
for (ModelInstance *mi : obj->instances) {
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
cont.emplace_back(get_arrange_poly(mi));
}
if (auto wti = get_wipe_tower(*m_plater))
m_selected.emplace_back(wti.get_arrange_polygon());
}
void ArrangeJob::prepare_selected() {
clear_input();
Model &model = m_plater->model();
double stride = bed_stride();
std::vector<const Selection::InstanceIdxsList *>
obj_sel(model.objects.size(), nullptr);
for (auto &s : m_plater->get_selection().get_content())
if (s.first < int(obj_sel.size()))
obj_sel[size_t(s.first)] = &s.second;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
ModelObject *mo = model.objects[oidx];
std::vector<bool> inst_sel(mo->instances.size(), false);
if (instlist)
for (auto inst_id : *instlist)
inst_sel[size_t(inst_id)] = true;
for (size_t i = 0; i < inst_sel.size(); ++i) {
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
ArrangePolygons &cont = mo->instances[i]->printable ?
(inst_sel[i] ? m_selected :
m_unselected) :
m_unprintable;
cont.emplace_back(std::move(ap));
}
}
if (auto wti = get_wipe_tower(*m_plater)) {
ArrangePolygon &&ap = get_arrange_poly(&wti);
m_plater->get_selection().is_wipe_tower() ?
m_selected.emplace_back(std::move(ap)) :
m_unselected.emplace_back(std::move(ap));
}
// If the selection was empty arrange everything
if (m_selected.empty()) m_selected.swap(m_unselected);
// The strides have to be removed from the fixed items. For the
// arrangeable (selected) items bed_idx is ignored and the
// translation is irrelevant.
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
}
void ArrangeJob::prepare()
{
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
}
void ArrangeJob::process()
{
static const auto arrangestr = _(L("Arranging"));
double dist = min_object_distance(*m_plater->config());
arrangement::ArrangeParams params;
params.min_obj_distance = scaled(dist);
auto count = unsigned(m_selected.size() + m_unprintable.size());
Points bedpts = get_bed_shape(*m_plater->config());
params.stopcondition = [this]() { return was_canceled(); };
try {
params.progressind = [this, count](unsigned st) {
st += m_unprintable.size();
if (st > 0) update_status(int(count - st), arrangestr);
};
arrangement::arrange(m_selected, m_unselected, bedpts, params);
params.progressind = [this, count](unsigned st) {
if (st > 0) update_status(int(count - st), arrangestr);
};
arrangement::arrange(m_unprintable, {}, bedpts, params);
} catch (std::exception & /*e*/) {
GUI::show_error(m_plater,
_(L("Could not arrange model objects! "
"Some geometries may be invalid.")));
}
// finalize just here.
update_status(int(count),
was_canceled() ? _(L("Arranging canceled."))
: _(L("Arranging done.")));
}
void ArrangeJob::finalize() {
// Ignore the arrange result if aborted.
if (was_canceled()) return;
// Unprintable items go to the last virtual bed
int beds = 0;
// Apply the arrange result to all selected objects
for (ArrangePolygon &ap : m_selected) {
beds = std::max(ap.bed_idx, beds);
ap.apply();
}
// Get the virtual beds from the unselected items
for (ArrangePolygon &ap : m_unselected)
beds = std::max(ap.bed_idx, beds);
// Move the unprintable items to the last virtual bed.
for (ArrangePolygon &ap : m_unprintable) {
ap.bed_idx += beds + 1;
ap.apply();
}
m_plater->update();
Job::finalize();
}
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater)
{
return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon();
}
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap)
{
WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast<double>(), ap.rotation);
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,77 @@
#ifndef ARRANGEJOB_HPP
#define ARRANGEJOB_HPP
#include "Job.hpp"
#include "libslic3r/Arrange.hpp"
namespace Slic3r { namespace GUI {
class Plater;
class ArrangeJob : public Job
{
Plater *m_plater;
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
// The gap between logical beds in the x axis expressed in ratio of
// the current bed width.
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
ArrangePolygons m_selected, m_unselected, m_unprintable;
// clear m_selected and m_unselected, reserve space for next usage
void clear_input();
// Stride between logical beds
double bed_stride() const;
// Set up arrange polygon for a ModelInstance and Wipe tower
template<class T> ArrangePolygon get_arrange_poly(T *obj) const
{
ArrangePolygon ap = obj->get_arrange_polygon();
ap.priority = 0;
ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) {
if (p.is_arranged()) {
Vec2d t = p.translation.cast<double>();
t.x() += p.bed_idx * bed_stride();
obj->apply_arrange_result(t, p.rotation);
}
};
return ap;
}
// Prepare all objects on the bed regardless of the selection
void prepare_all();
// Prepare the selected and unselected items separately. If nothing is
// selected, behaves as if everything would be selected.
void prepare_selected();
protected:
void prepare() override;
public:
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: Job{std::move(pri)}, m_plater{plater}
{}
int status_range() const override
{
return int(m_selected.size() + m_unprintable.size());
}
void process() override;
void finalize() override;
};
arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &);
void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap);
}} // namespace Slic3r::GUI
#endif // ARRANGEJOB_HPP

121
src/slic3r/GUI/Jobs/Job.cpp Normal file
View file

@ -0,0 +1,121 @@
#include <algorithm>
#include "Job.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
void GUI::Job::run()
{
m_running.store(true);
process();
m_running.store(false);
// ensure to call the last status to finalize the job
update_status(status_range(), "");
}
void GUI::Job::update_status(int st, const wxString &msg)
{
auto evt = new wxThreadEvent();
evt->SetInt(st);
evt->SetString(msg);
wxQueueEvent(this, evt);
}
GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
: m_progress(std::move(pri))
{
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
auto msg = evt.GetString();
if (!msg.empty())
m_progress->set_status_text(msg.ToUTF8().data());
if (m_finalized) return;
m_progress->set_progress(evt.GetInt());
if (evt.GetInt() == status_range()) {
// set back the original range and cancel callback
m_progress->set_range(m_range);
m_progress->set_cancel_callback();
wxEndBusyCursor();
finalize();
// dont do finalization again for the same process
m_finalized = true;
}
});
}
void GUI::Job::start()
{ // Start the job. No effect if the job is already running
if (!m_running.load()) {
prepare();
// Save the current status indicatior range and push the new one
m_range = m_progress->get_range();
m_progress->set_range(status_range());
// init cancellation flag and set the cancel callback
m_canceled.store(false);
m_progress->set_cancel_callback(
[this]() { m_canceled.store(true); });
m_finalized = false;
// Changing cursor to busy
wxBeginBusyCursor();
try { // Execute the job
m_thread = create_thread([this] { this->run(); });
} catch (std::exception &) {
update_status(status_range(),
_(L("ERROR: not enough resources to "
"execute a new job.")));
}
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
}
}
bool GUI::Job::join(int timeout_ms)
{
if (!m_thread.joinable()) return true;
if (timeout_ms <= 0)
m_thread.join();
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
return false;
return true;
}
void GUI::ExclusiveJobGroup::start(size_t jid) {
assert(jid < m_jobs.size());
stop_all();
m_jobs[jid]->start();
}
void GUI::ExclusiveJobGroup::join_all(int wait_ms)
{
std::vector<bool> aborted(m_jobs.size(), false);
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
aborted[jid] = m_jobs[jid]->join(wait_ms);
if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
}
bool GUI::ExclusiveJobGroup::is_any_running() const
{
return std::any_of(m_jobs.begin(), m_jobs.end(),
[](const std::unique_ptr<GUI::Job> &j) {
return j->is_running();
});
}
}

110
src/slic3r/GUI/Jobs/Job.hpp Normal file
View file

@ -0,0 +1,110 @@
#ifndef JOB_HPP
#define JOB_HPP
#include <atomic>
#include <slic3r/Utils/Thread.hpp>
#include <slic3r/GUI/I18N.hpp>
#include "ProgressIndicator.hpp"
#include <wx/event.h>
#include <boost/thread.hpp>
namespace Slic3r { namespace GUI {
// A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
// state in the status progress indicator. On the other hand they are
// separated from the background slicing process. Ideally, these jobs should
// run when the background process is not running.
//
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class Job : public wxEvtHandler
{
int m_range = 100;
boost::thread m_thread;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
std::shared_ptr<ProgressIndicator> m_progress;
void run();
protected:
// status range for a particular job
virtual int status_range() const { return 100; }
// status update, to be used from the work thread (process() method)
void update_status(int st, const wxString &msg = "");
bool was_canceled() const { return m_canceled.load(); }
// Launched just before start(), a job can use it to prepare internals
virtual void prepare() {}
// Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize() { m_finalized = true; }
public:
Job(std::shared_ptr<ProgressIndicator> pri);
bool is_finalized() const { return m_finalized; }
Job(const Job &) = delete;
Job(Job &&) = delete;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = delete;
virtual void process() = 0;
void start();
// To wait for the running job and join the threads. False is
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
bool join(int timeout_ms = 0);
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
};
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started.
class ExclusiveJobGroup
{
static const int ABORT_WAIT_MAX_MS = 10000;
std::vector<std::unique_ptr<GUI::Job>> m_jobs;
protected:
virtual void before_start() {}
public:
virtual ~ExclusiveJobGroup() = default;
size_t add_job(std::unique_ptr<GUI::Job> &&job)
{
m_jobs.emplace_back(std::move(job));
return m_jobs.size() - 1;
}
void start(size_t jid);
void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
void join_all(int wait_ms = 0);
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
bool is_any_running() const;
};
}} // namespace Slic3r::GUI
#endif // JOB_HPP

View file

@ -0,0 +1,68 @@
#include "RotoptimizeJob.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
#include "slic3r/GUI/Plater.hpp"
namespace Slic3r { namespace GUI {
void RotoptimizeJob::process()
{
int obj_idx = m_plater->get_selected_object_idx();
if (obj_idx < 0) { return; }
ModelObject *o = m_plater->model().objects[size_t(obj_idx)];
auto r = sla::find_best_rotation(
*o,
.005f,
[this](unsigned s) {
if (s < 100)
update_status(int(s),
_(L("Searching for optimal orientation")));
},
[this]() { return was_canceled(); });
double mindist = 6.0; // FIXME
if (!was_canceled()) {
for(ModelInstance * oi : o->instances) {
oi->set_rotation({r[X], r[Y], r[Z]});
auto trmatrix = oi->get_transformation().get_matrix();
Polygon trchull = o->convex_hull_2d(trmatrix);
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
double phi = rotbb.angle_to_X();
// The box should be landscape
if(rotbb.width() < rotbb.height()) phi += PI / 2;
Vec3d rt = oi->get_rotation(); rt(Z) += phi;
oi->set_rotation(rt);
}
m_plater->find_new_position(o->instances, scaled(mindist));
// Correct the z offset of the object which was corrupted be
// the rotation
o->ensure_on_bed();
}
update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
_(L("Orientation found.")));
}
void RotoptimizeJob::finalize()
{
if (!was_canceled())
m_plater->update();
Job::finalize();
}
}}

View file

@ -0,0 +1,24 @@
#ifndef ROTOPTIMIZEJOB_HPP
#define ROTOPTIMIZEJOB_HPP
#include "Job.hpp"
namespace Slic3r { namespace GUI {
class Plater;
class RotoptimizeJob : public Job
{
Plater *m_plater;
public:
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: Job{std::move(pri)}, m_plater{plater}
{}
void process() override;
void finalize() override;
};
}} // namespace Slic3r::GUI
#endif // ROTOPTIMIZEJOB_HPP

View file

@ -0,0 +1,226 @@
#include "SLAImportJob.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/AppConfig.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/Utils/SLAImport.hpp"
#include <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/filename.h>
#include <wx/filepicker.h>
namespace Slic3r { namespace GUI {
enum class Sel { modelAndProfile, profileOnly, modelOnly};
class ImportDlg: public wxDialog {
wxFilePickerCtrl *m_filepicker;
wxComboBox *m_import_dropdown, *m_quality_dropdown;
public:
ImportDlg(Plater *plater)
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
{
auto szvert = new wxBoxSizer{wxVERTICAL};
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
"SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP",
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
szfilepck->Add(new wxStaticText(this, wxID_ANY, _(L("Import file: "))), 0, wxALIGN_CENTER);
szfilepck->Add(m_filepicker, 1);
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
static const std::vector<wxString> inp_choices = {
_(L("Import model and profile")),
_(L("Import profile only")),
_(L("Import model only"))
};
m_import_dropdown = new wxComboBox(
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_import_dropdown);
szchoices->Add(new wxStaticText(this, wxID_ANY, _(L("Quality: "))), 0, wxALIGN_CENTER | wxALL, 5);
static const std::vector<wxString> qual_choices = {
_(L("Accurate")),
_(L("Balanced")),
_(L("Quick"))
};
m_quality_dropdown = new wxComboBox(
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_quality_dropdown);
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
if (get_selection() == Sel::profileOnly)
m_quality_dropdown->Disable();
else m_quality_dropdown->Enable();
});
szvert->Add(szchoices, 0, wxALL, 5);
szvert->AddStretchSpacer(1);
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
szbtn->Add(new wxButton{this, wxID_CANCEL});
szbtn->Add(new wxButton{this, wxID_OK});
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
SetSizerAndFit(szvert);
}
Sel get_selection() const
{
int sel = m_import_dropdown->GetSelection();
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
}
Vec2i get_marchsq_windowsize() const
{
enum { Accurate, Balanced, Fast};
switch(m_quality_dropdown->GetSelection())
{
case Fast: return {8, 8};
case Balanced: return {4, 4};
default:
case Accurate:
return {2, 2};
}
}
wxString get_path() const
{
return m_filepicker->GetPath();
}
};
class SLAImportJob::priv {
public:
Plater *plater;
Sel sel = Sel::modelAndProfile;
TriangleMesh mesh;
DynamicPrintConfig profile;
wxString path;
Vec2i win = {2, 2};
std::string err;
priv(Plater *plt): plater{plt} {}
};
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: Job{std::move(pri)}, p{std::make_unique<priv>(plater)}
{}
SLAImportJob::~SLAImportJob() = default;
void SLAImportJob::process()
{
auto progr = [this](int s) {
if (s < 100) update_status(int(s), _(L("Importing SLA archive")));
return !was_canceled();
};
if (p->path.empty()) return;
std::string path = p->path.ToUTF8().data();
try {
switch (p->sel) {
case Sel::modelAndProfile:
import_sla_archive(path, p->win, p->mesh, p->profile, progr);
break;
case Sel::modelOnly:
import_sla_archive(path, p->win, p->mesh, progr);
break;
case Sel::profileOnly:
import_sla_archive(path, p->profile);
break;
}
} catch (std::exception &ex) {
p->err = ex.what();
}
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
_(L("Importing done.")));
}
void SLAImportJob::reset()
{
p->sel = Sel::modelAndProfile;
p->mesh = {};
p->profile = {};
p->win = {2, 2};
p->path.Clear();
}
void SLAImportJob::prepare()
{
reset();
ImportDlg dlg{p->plater};
if (dlg.ShowModal() == wxID_OK) {
auto path = dlg.get_path();
auto nm = wxFileName(path);
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8();
p->sel = dlg.get_selection();
p->win = dlg.get_marchsq_windowsize();
} else {
p->path = "";
}
}
void SLAImportJob::finalize()
{
// Ignore the arrange result if aborted.
if (was_canceled()) return;
if (!p->err.empty()) {
show_error(p->plater, p->err);
p->err = "";
return;
}
std::string name = wxFileName(p->path).GetName().ToUTF8().data();
if (!p->profile.empty()) {
const ModelObjectPtrs& objects = p->plater->model().objects;
for (auto object : objects)
if (object->volumes.size() > 1)
{
Slic3r::GUI::show_info(nullptr,
_(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" +
_(L("Please check your object list before preset changing.")),
_(L("Attention!")) );
return;
}
DynamicPrintConfig config = {};
config.apply(SLAFullPrintConfig::defaults());
config += std::move(p->profile);
wxGetApp().preset_bundle->load_config_model(name, std::move(config));
wxGetApp().load_current_presets();
}
if (!p->mesh.empty())
p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name);
reset();
}
}}

View file

@ -0,0 +1,31 @@
#ifndef SLAIMPORTJOB_HPP
#define SLAIMPORTJOB_HPP
#include "Job.hpp"
namespace Slic3r { namespace GUI {
class Plater;
class SLAImportJob : public Job {
class priv;
std::unique_ptr<priv> p;
public:
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
~SLAImportJob();
void process() override;
void reset();
protected:
void prepare() override;
void finalize() override;
};
}} // namespace Slic3r::GUI
#endif // SLAIMPORTJOB_HPP

View file

@ -26,6 +26,7 @@
#include "GUI_ObjectList.hpp"
#include "Mouse3DController.hpp"
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#include "I18N.hpp"
#include <fstream>
@ -236,7 +237,8 @@ void MainFrame::shutdown()
// Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater.
wxGetApp().removable_drive_manager()->shutdown();
//stop listening for messages from other instances
wxGetApp().other_instance_message_handler()->shutdown();
// Save the slic3r.ini.Usually the ini file is saved from "on idle" callback,
// but in rare cases it may not have been called yet.
wxGetApp().app_config->save();
@ -603,6 +605,11 @@ void MainFrame::init_menubar()
append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")),
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")),
[this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr,

View file

@ -684,6 +684,9 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
opt_key == "fill_pattern" ) {
ret = static_cast<int>(config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value);
}
else if (opt_key.compare("ironing_type") == 0 ) {
ret = static_cast<int>(config.option<ConfigOptionEnum<IroningType>>(opt_key)->value);
}
else if (opt_key.compare("gcode_flavor") == 0 ) {
ret = static_cast<int>(config.option<ConfigOptionEnum<GCodeFlavor>>(opt_key)->value);
}

View file

@ -36,7 +36,6 @@
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Print.hpp"
@ -44,13 +43,6 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
//#include "libslic3r/ClipperUtils.hpp"
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
// #include "libnest2d/backends/clipper/geometries.hpp"
// #include "libnest2d/utils/rotcalipers.hpp"
#include "libslic3r/MinAreaBoundingBox.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
@ -69,7 +61,9 @@
#include "Camera.hpp"
#include "Mouse3DController.hpp"
#include "Tab.hpp"
#include "Job.hpp"
#include "Jobs/ArrangeJob.hpp"
#include "Jobs/RotoptimizeJob.hpp"
#include "Jobs/SLAImportJob.hpp"
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp"
@ -80,6 +74,7 @@
#include "../Utils/FixModelByWin10.hpp"
#include "../Utils/UndoRedo.hpp"
#include "RemovableDriveManager.hpp"
#include "InstanceCheck.hpp"
#if ENABLE_NON_STATIC_CANVAS_MANAGER
#ifdef __APPLE__
@ -1551,311 +1546,44 @@ struct Plater::priv
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
// Cache the wti info
class WipeTower: public GLCanvas3D::WipeTowerInfo {
using ArrangePolygon = arrangement::ArrangePolygon;
friend priv;
public:
void apply_arrange_result(const Vec2d& tr, double rotation)
{
m_pos = unscaled(tr); m_rotation = rotation;
apply_wipe_tower();
}
ArrangePolygon get_arrange_polygon() const
{
Polygon p({
{coord_t(0), coord_t(0)},
{scaled(m_bb_size(X)), coord_t(0)},
{scaled(m_bb_size)},
{coord_t(0), scaled(m_bb_size(Y))},
{coord_t(0), coord_t(0)},
});
ArrangePolygon ret;
ret.poly.contour = std::move(p);
ret.translation = scaled(m_pos);
ret.rotation = m_rotation;
ret.priority++;
return ret;
}
} wipetower;
WipeTower& updated_wipe_tower() {
auto wti = view3D->get_canvas3d()->get_wipe_tower_info();
wipetower.m_pos = wti.pos();
wipetower.m_rotation = wti.rotation();
wipetower.m_bb_size = wti.bb_size();
return wipetower;
}
// A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
// state in the status progress indicator. On the other hand they are
// separated from the background slicing process. Ideally, these jobs should
// run when the background process is not running.
//
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class PlaterJob: public Job
{
priv *m_plater;
protected:
priv & plater() { return *m_plater; }
const priv &plater() const { return *m_plater; }
// Launched when the job is finished. It refreshes the 3Dscene by def.
void finalize() override
{
// Do a full refresh of scene tree, including regenerating
// all the GLVolumes. FIXME The update function shall just
// reload the modified matrices.
if (!Job::was_canceled())
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
Job::finalize();
}
public:
PlaterJob(priv *_plater)
: Job(_plater->statusbar()), m_plater(_plater)
{}
};
enum class Jobs : size_t {
Arrange,
Rotoptimize
};
class ArrangeJob : public PlaterJob
{
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
// The gap between logical beds in the x axis expressed in ratio of
// the current bed width.
static const constexpr double LOGICAL_BED_GAP = 1. / 5.;
ArrangePolygons m_selected, m_unselected, m_unprintable;
// clear m_selected and m_unselected, reserve space for next usage
void clear_input() {
const Model &model = plater().model;
size_t count = 0, cunprint = 0; // To know how much space to reserve
for (auto obj : model.objects)
for (auto mi : obj->instances)
mi->printable ? count++ : cunprint++;
m_selected.clear();
m_unselected.clear();
m_unprintable.clear();
m_selected.reserve(count + 1 /* for optional wti */);
m_unselected.reserve(count + 1 /* for optional wti */);
m_unprintable.reserve(cunprint /* for optional wti */);
}
// Stride between logical beds
double bed_stride() const {
double bedwidth = plater().bed_shape_bb().size().x();
return scaled<double>((1. + LOGICAL_BED_GAP) * bedwidth);
}
// Set up arrange polygon for a ModelInstance and Wipe tower
template<class T> ArrangePolygon get_arrange_poly(T *obj) const {
ArrangePolygon ap = obj->get_arrange_polygon();
ap.priority = 0;
ap.bed_idx = ap.translation.x() / bed_stride();
ap.setter = [obj, this](const ArrangePolygon &p) {
if (p.is_arranged()) {
Vec2d t = p.translation.cast<double>();
t.x() += p.bed_idx * bed_stride();
obj->apply_arrange_result(t, p.rotation);
}
};
return ap;
}
// Prepare all objects on the bed regardless of the selection
void prepare_all() {
clear_input();
for (ModelObject *obj: plater().model.objects)
for (ModelInstance *mi : obj->instances) {
ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable;
cont.emplace_back(get_arrange_poly(mi));
}
auto& wti = plater().updated_wipe_tower();
if (wti) m_selected.emplace_back(get_arrange_poly(&wti));
}
// Prepare the selected and unselected items separately. If nothing is
// selected, behaves as if everything would be selected.
void prepare_selected() {
clear_input();
Model &model = plater().model;
coord_t stride = bed_stride();
std::vector<const Selection::InstanceIdxsList *>
obj_sel(model.objects.size(), nullptr);
for (auto &s : plater().get_selection().get_content())
if (s.first < int(obj_sel.size()))
obj_sel[size_t(s.first)] = &s.second;
// Go through the objects and check if inside the selection
for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) {
const Selection::InstanceIdxsList * instlist = obj_sel[oidx];
ModelObject *mo = model.objects[oidx];
std::vector<bool> inst_sel(mo->instances.size(), false);
if (instlist)
for (auto inst_id : *instlist)
inst_sel[size_t(inst_id)] = true;
for (size_t i = 0; i < inst_sel.size(); ++i) {
ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]);
ArrangePolygons &cont = mo->instances[i]->printable ?
(inst_sel[i] ? m_selected :
m_unselected) :
m_unprintable;
cont.emplace_back(std::move(ap));
}
}
auto& wti = plater().updated_wipe_tower();
if (wti) {
ArrangePolygon &&ap = get_arrange_poly(&wti);
plater().get_selection().is_wipe_tower() ?
m_selected.emplace_back(std::move(ap)) :
m_unselected.emplace_back(std::move(ap));
}
// If the selection was empty arrange everything
if (m_selected.empty()) m_selected.swap(m_unselected);
// The strides have to be removed from the fixed items. For the
// arrangeable (selected) items bed_idx is ignored and the
// translation is irrelevant.
for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride;
}
protected:
void prepare() override
{
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
}
public:
using PlaterJob::PlaterJob;
int status_range() const override
{
return int(m_selected.size() + m_unprintable.size());
}
void process() override;
void finalize() override {
// Ignore the arrange result if aborted.
if (was_canceled()) return;
// Unprintable items go to the last virtual bed
int beds = 0;
// Apply the arrange result to all selected objects
for (ArrangePolygon &ap : m_selected) {
beds = std::max(ap.bed_idx, beds);
ap.apply();
}
// Get the virtual beds from the unselected items
for (ArrangePolygon &ap : m_unselected)
beds = std::max(ap.bed_idx, beds);
// Move the unprintable items to the last virtual bed.
for (ArrangePolygon &ap : m_unprintable) {
ap.bed_idx += beds + 1;
ap.apply();
}
plater().update();
}
};
class RotoptimizeJob : public PlaterJob
{
public:
using PlaterJob::PlaterJob;
void process() override;
};
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started.
class ExclusiveJobGroup {
static const int ABORT_WAIT_MAX_MS = 10000;
priv * m_plater;
ArrangeJob arrange_job{m_plater};
RotoptimizeJob rotoptimize_job{m_plater};
// To create a new job, just define a new subclass of Job, implement
// the process and the optional prepare() and finalize() methods
// Register the instance of the class in the m_jobs container
// if it cannot run concurrently with other jobs in this group
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
rotoptimize_job};
// started. It is up the the plater to ensure that the background slicing
// can't be restarted while a ui job is still running.
class Jobs: public ExclusiveJobGroup
{
priv *m;
size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id;
void before_start() override { m->background_process.stop(); }
public:
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
void start(Jobs jid) {
m_plater->background_process.stop();
stop_all();
m_jobs[size_t(jid)].get().start();
}
void cancel_all() { for (Job& j : m_jobs) j.cancel(); }
void join_all(int wait_ms = 0)
Jobs(priv *_m) : m(_m)
{
std::vector<bool> aborted(m_jobs.size(), false);
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
aborted[jid] = m_jobs[jid].get().join(wait_ms);
if (!all_of(aborted))
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q));
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q));
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q));
}
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; }
bool is_any_running() const
void arrange()
{
return std::any_of(m_jobs.begin(),
m_jobs.end(),
[](const Job &j) { return j.is_running(); });
m->take_snapshot(_(L("Arrange")));
start(m_arrange_id);
}
} m_ui_jobs{this};
void optimize_rotation()
{
m->take_snapshot(_(L("Optimize Rotation")));
start(m_rotoptimize_id);
}
void import_sla_arch()
{
m->take_snapshot(_(L("Import SLA archive")));
start(m_sla_import_id);
}
} m_ui_jobs;
bool delayed_scene_refresh;
std::string delayed_error_message;
@ -1874,10 +1602,10 @@ struct Plater::priv
priv(Plater *q, MainFrame *main_frame);
~priv();
enum class UpdateParams {
FORCE_FULL_SCREEN_REFRESH = 1,
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
enum class UpdateParams {
FORCE_FULL_SCREEN_REFRESH = 1,
FORCE_BACKGROUND_PROCESSING_UPDATE = 2,
POSTPONE_VALIDATION_ERROR_MESSAGE = 4,
};
void update(unsigned int flags = 0);
void select_view(const std::string& direction);
@ -1916,9 +1644,7 @@ struct Plater::priv
std::string get_config(const std::string &key) const;
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
arrangement::BedShapeHint get_bed_shape_hint() const;
void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
wxString get_export_file(GUI::FileType file_type);
@ -1936,8 +1662,6 @@ struct Plater::priv
void delete_object_from_model(size_t obj_idx);
void reset();
void mirror(Axis axis);
void arrange();
void sla_optimize_rotation();
void split_object();
void split_volume();
void scale_selection_to_fit_print_volume();
@ -2104,6 +1828,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
"support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers"
}))
, sidebar(new Sidebar(q))
, m_ui_jobs(this)
, delayed_scene_refresh(false)
, view_toolbar(GLToolbar::Radio, "View")
, m_project_filename(wxEmptyString)
@ -2179,14 +1904,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas();
// 3DScene events:
view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); });
view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this);
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); });
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); });
@ -2212,7 +1938,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
@ -2292,6 +2018,28 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// Initialize the Undo / Redo stack with a first snapshot.
this->take_snapshot(_L("New Project"));
this->q->Bind(EVT_LOAD_MODEL_OTHER_INSTANCE, [this](LoadFromOtherInstanceEvent &evt) {
BOOST_LOG_TRIVIAL(debug) << "received load from other instance event ";
this->load_files(evt.data, true, true);
});
this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
BOOST_LOG_TRIVIAL(debug) << "prusaslicer window going forward";
//this code maximize app window on Fedora
wxGetApp().mainframe->Iconize(false);
if (wxGetApp().mainframe->IsMaximized())
wxGetApp().mainframe->Maximize(true);
else
wxGetApp().mainframe->Maximize(false);
//this code (without code above) maximize window on Ubuntu
wxGetApp().mainframe->Restore();
wxGetApp().GetTopWindow()->SetFocus(); // focus on my window
wxGetApp().GetTopWindow()->Raise(); // bring window to front
wxGetApp().GetTopWindow()->Show(true); // show the window
});
wxGetApp().other_instance_message_handler()->init(this->q);
// collapse sidebar according to saved value
sidebar->collapse(wxGetApp().app_config->get("collapsed_sidebar") == "1");
}
@ -2883,40 +2631,12 @@ void Plater::priv::mirror(Axis axis)
view3D->mirror_selection(axis);
}
void Plater::priv::arrange()
{
this->take_snapshot(_L("Arrange"));
m_ui_jobs.start(Jobs::Arrange);
}
// This method will find an optimal orientation for the currently selected item
// Very similar in nature to the arrange method above...
void Plater::priv::sla_optimize_rotation() {
this->take_snapshot(_L("Optimize Rotation"));
m_ui_jobs.start(Jobs::Rotoptimize);
}
arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const {
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
assert(bed_shape_opt);
if (!bed_shape_opt) return {};
auto &bedpoints = bed_shape_opt->values;
Polyline bedpoly; bedpoly.points.reserve(bedpoints.size());
for (auto &v : bedpoints) bedpoly.append(scaled(v));
return arrangement::BedShapeHint(bedpoly);
}
void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
void Plater::find_new_position(const ModelInstancePtrs &instances,
coord_t min_d)
{
arrangement::ArrangePolygons movable, fixed;
for (const ModelObject *mo : model.objects)
for (const ModelObject *mo : p->model.objects)
for (const ModelInstance *inst : mo->instances) {
auto it = std::find(instances.begin(), instances.end(), inst);
auto arrpoly = inst->get_arrange_polygon();
@ -2926,11 +2646,12 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
else
movable.emplace_back(std::move(arrpoly));
}
if (updated_wipe_tower())
fixed.emplace_back(wipetower.get_arrange_polygon());
arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint());
if (p->view3D->get_canvas3d()->get_wipe_tower_info())
fixed.emplace_back(get_wipe_tower_arrangepoly(*this));
arrangement::arrange(movable, fixed, get_bed_shape(*config()),
arrangement::ArrangeParams{min_d});
for (size_t i = 0; i < instances.size(); ++i)
if (movable[i].bed_idx == 0)
@ -2938,95 +2659,6 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances,
movable[i].rotation);
}
void Plater::priv::ArrangeJob::process() {
static const auto arrangestr = _L("Arranging");
// FIXME: I don't know how to obtain the minimum distance, it depends
// on printer technology. I guess the following should work but it crashes.
double dist = 6; // PrintConfig::min_object_distance(config);
if (plater().printer_technology == ptFFF) {
dist = PrintConfig::min_object_distance(plater().config);
}
coord_t min_d = scaled(dist);
auto count = unsigned(m_selected.size() + m_unprintable.size());
arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint();
auto stopfn = [this]() { return was_canceled(); };
try {
arrangement::arrange(m_selected, m_unselected, min_d, bedshape,
[this, count](unsigned st) {
st += m_unprintable.size();
if (st > 0) update_status(int(count - st), arrangestr);
}, stopfn);
arrangement::arrange(m_unprintable, {}, min_d, bedshape,
[this, count](unsigned st) {
if (st > 0) update_status(int(count - st), arrangestr);
}, stopfn);
} catch (std::exception & /*e*/) {
GUI::show_error(plater().q,
_L("Could not arrange model objects! "
"Some geometries may be invalid."));
}
// finalize just here.
update_status(int(count),
was_canceled() ? _L("Arranging canceled.")
: _L("Arranging done."));
}
void Plater::priv::RotoptimizeJob::process()
{
int obj_idx = plater().get_selected_object_idx();
if (obj_idx < 0) { return; }
ModelObject *o = plater().model.objects[size_t(obj_idx)];
auto r = sla::find_best_rotation(
*o,
.005f,
[this](unsigned s) {
if (s < 100)
update_status(int(s),
_L("Searching for optimal orientation"));
},
[this]() { return was_canceled(); });
double mindist = 6.0; // FIXME
if (!was_canceled()) {
for(ModelInstance * oi : o->instances) {
oi->set_rotation({r[X], r[Y], r[Z]});
auto trmatrix = oi->get_transformation().get_matrix();
Polygon trchull = o->convex_hull_2d(trmatrix);
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
double r = rotbb.angle_to_X();
// The box should be landscape
if(rotbb.width() < rotbb.height()) r += PI / 2;
Vec3d rt = oi->get_rotation(); rt(Z) += r;
oi->set_rotation(rt);
}
plater().find_new_position(o->instances, scaled(mindist));
// Correct the z offset of the object which was corrupted be
// the rotation
o->ensure_on_bed();
}
update_status(100,
was_canceled() ? _L("Orientation search canceled.")
: _L("Orientation found."));
}
void Plater::priv::split_object()
{
int obj_idx = get_selected_object_idx();
@ -3667,7 +3299,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
}
// update plater with new config
wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config());
q->on_config_change(wxGetApp().preset_bundle->full_config());
if (preset_type == Preset::TYPE_PRINTER) {
/* Settings list can be changed after printer preset changing, so
* update all settings items for all item had it.
@ -4115,8 +3747,12 @@ bool Plater::priv::complit_init_sla_object_menu()
sla_object_menu.AppendSeparator();
// Add the automatic rotation sub-menu
append_menu_item(&sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."),
[this](wxCommandEvent&) { sla_optimize_rotation(); });
append_menu_item(
&sla_object_menu, wxID_ANY, _(L("Optimize orientation")),
_(L("Optimize the rotation of the object for better print results.")),
[this](wxCommandEvent &) {
m_ui_jobs.optimize_rotation();
});
return true;
}
@ -4720,6 +4356,11 @@ void Plater::add_model()
load_files(paths, true, false);
}
void Plater::import_sl1_archive()
{
p->m_ui_jobs.import_sla_arch();
}
void Plater::extract_config_from_project()
{
wxString input_file;
@ -4815,7 +4456,7 @@ void Plater::increase_instances(size_t num)
sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num);
if (p->get_config("autocenter") == "1")
p->arrange();
arrange();
p->update();
@ -5564,6 +5205,11 @@ bool Plater::is_export_gcode_scheduled() const
return p->background_process.is_export_scheduled();
}
const Selection &Plater::get_selection() const
{
return p->get_selection();
}
int Plater::get_selected_object_idx()
{
return p->get_selected_object_idx();
@ -5589,6 +5235,11 @@ BoundingBoxf Plater::bed_shape_bb() const
return p->bed_shape_bb();
}
void Plater::arrange()
{
p->m_ui_jobs.arrange();
}
void Plater::set_current_canvas_as_dirty()
{
p->set_current_canvas_as_dirty();
@ -5611,6 +5262,8 @@ PrinterTechnology Plater::printer_technology() const
return p->printer_technology;
}
const DynamicPrintConfig * Plater::config() const { return p->config; }
void Plater::set_printer_technology(PrinterTechnology printer_technology)
{
p->printer_technology = printer_technology;

View file

@ -9,8 +9,10 @@
#include <wx/bmpcbox.h>
#include "Preset.hpp"
#include "Selection.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "Jobs/Job.hpp"
#include "wxExtensions.hpp"
#include "Search.hpp"
@ -166,6 +168,7 @@ public:
void load_project();
void load_project(const wxString& filename);
void add_model();
void import_sl1_archive();
void extract_config_from_project();
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true);
@ -265,12 +268,16 @@ public:
void set_project_filename(const wxString& filename);
bool is_export_gcode_scheduled() const;
const Selection& get_selection() const;
int get_selected_object_idx();
bool is_single_full_object_selection() const;
GLCanvas3D* canvas3D();
GLCanvas3D* get_current_canvas3D();
BoundingBoxf bed_shape_bb() const;
void arrange();
void find_new_position(const ModelInstancePtrs &instances, coord_t min_d);
void set_current_canvas_as_dirty();
#if ENABLE_NON_STATIC_CANVAS_MANAGER
@ -279,6 +286,7 @@ public:
#endif // ENABLE_NON_STATIC_CANVAS_MANAGER
PrinterTechnology printer_technology() const;
const DynamicPrintConfig * config() const;
void set_printer_technology(PrinterTechnology printer_technology);
void copy_selection_to_clipboard();
@ -385,6 +393,7 @@ private:
bool m_was_scheduled;
};
}}
} // namespace GUI
} // namespace Slic3r
#endif

View file

@ -100,6 +100,13 @@ void PreferencesDialog::build()
option = Option (def,"show_incompatible_presets");
m_optgroup_general->append_single_option_line(option);
def.label = L("Single Instance");
def.type = coBool;
def.tooltip = L("If this is enabled, when staring PrusaSlicer and another instance is running, that instance will be reactivated instead.");
def.set_default_value(new ConfigOptionBool{ app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false });
option = Option(def, "single_instance");
m_optgroup_general->append_single_option_line(option);
#if __APPLE__
def.label = L("Use Retina resolution for the 3D scene");
def.type = coBool;
@ -177,6 +184,8 @@ void PreferencesDialog::accept()
app_config->set(it->first, it->second);
}
app_config->save();
EndModal(wxID_OK);
// Nothify the UI to update itself from the ini file.

View file

@ -405,8 +405,9 @@ const std::vector<std::string>& Preset::print_options()
"extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs",
"seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern",
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle",
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "max_print_speed",
"max_volumetric_speed",
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
"max_print_speed", "max_volumetric_speed",
#ifdef HAS_PRESSURE_EQUALIZER
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
#endif /* HAS_PRESSURE_EQUALIZER */

View file

@ -6,7 +6,7 @@
#include <functional>
#include <string>
#include "ProgressIndicator.hpp"
#include "Jobs/ProgressIndicator.hpp"
class wxTimer;
class wxGauge;

View file

@ -1283,6 +1283,12 @@ void TabPrint::build()
optgroup->append_single_option_line("top_fill_pattern");
optgroup->append_single_option_line("bottom_fill_pattern");
optgroup = page->new_optgroup(L("Ironing"));
optgroup->append_single_option_line("ironing");
optgroup->append_single_option_line("ironing_type");
optgroup->append_single_option_line("ironing_flowrate");
optgroup->append_single_option_line("ironing_spacing");
optgroup = page->new_optgroup(L("Reducing printing time"));
optgroup->append_single_option_line("infill_every_layers");
optgroup->append_single_option_line("infill_only_where_needed");
@ -1343,6 +1349,7 @@ void TabPrint::build()
optgroup->append_single_option_line("support_material_interface_speed");
optgroup->append_single_option_line("bridge_speed");
optgroup->append_single_option_line("gap_fill_speed");
optgroup->append_single_option_line("ironing_speed");
optgroup = page->new_optgroup(L("Speed for non-print moves"));
optgroup->append_single_option_line("travel_speed");