mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-23 00:31:11 -06:00
Fixed conflicts after merge with master
This commit is contained in:
commit
39ec1a6318
192 changed files with 7204 additions and 2296 deletions
|
@ -57,6 +57,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Gizmos/GLGizmoPainterBase.hpp
|
||||
GUI/Gizmos/GLGizmoSeam.cpp
|
||||
GUI/Gizmos/GLGizmoSeam.hpp
|
||||
GUI/Gizmos/GLGizmoSimplify.cpp
|
||||
GUI/Gizmos/GLGizmoSimplify.hpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.cpp
|
||||
GUI/Gizmos/GLGizmoMmuSegmentation.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
|
@ -266,7 +268,7 @@ if(APPLE)
|
|||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY})
|
||||
endif()
|
||||
|
||||
if (SLIC3R_STATIC AND UNIX AND NOT APPLE)
|
||||
if (SLIC3R_STATIC AND NOT SLIC3R_STATIC_EXCLUDE_CURL AND UNIX AND NOT APPLE)
|
||||
target_compile_definitions(libslic3r_gui PRIVATE OPENSSL_CERT_OVERRIDE)
|
||||
endif ()
|
||||
|
||||
|
|
|
@ -8,14 +8,23 @@
|
|||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/FileParserError.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "../GUI/GUI.hpp"
|
||||
#include "../GUI/GUI_App.hpp"
|
||||
#include "../GUI/I18N.hpp"
|
||||
#include "../GUI/MainFrame.hpp"
|
||||
|
||||
#include <wx/richmsgdlg.h>
|
||||
|
||||
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
|
||||
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
|
||||
|
||||
|
@ -358,11 +367,12 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src
|
|||
{
|
||||
if (! boost::filesystem::is_directory(path_dst) &&
|
||||
! boost::filesystem::create_directory(path_dst))
|
||||
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
|
||||
throw Slic3r::RuntimeError(std::string("PrusaSlicer was unable to create a directory at ") + path_dst.string());
|
||||
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
|
||||
if (Slic3r::is_ini_file(dir_entry))
|
||||
boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
|
||||
if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message));
|
||||
}
|
||||
|
||||
static void delete_existing_ini_files(const boost::filesystem::path &path)
|
||||
|
@ -413,7 +423,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
|
|||
++ it;
|
||||
// Read the active config bundle, parse the config version.
|
||||
PresetBundle bundle;
|
||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly);
|
||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
for (const auto &vp : bundle.vendors)
|
||||
if (vp.second.id == cfg.name)
|
||||
cfg.version.config_version = vp.second.config_version;
|
||||
|
@ -433,14 +443,27 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
|
|||
}
|
||||
|
||||
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
|
||||
boost::filesystem::create_directory(snapshot_dir);
|
||||
|
||||
// Backup the presets.
|
||||
for (const char *subdir : snapshot_subdirs)
|
||||
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
|
||||
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
|
||||
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
|
||||
m_snapshots.emplace_back(std::move(snapshot));
|
||||
try {
|
||||
boost::filesystem::create_directory(snapshot_dir);
|
||||
|
||||
// Backup the presets.
|
||||
for (const char *subdir : snapshot_subdirs)
|
||||
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
|
||||
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
|
||||
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
|
||||
m_snapshots.emplace_back(std::move(snapshot));
|
||||
} catch (...) {
|
||||
if (boost::filesystem::is_directory(snapshot_dir)) {
|
||||
try {
|
||||
// Clean up partially copied snapshot.
|
||||
boost::filesystem::remove_all(snapshot_dir);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir;
|
||||
}
|
||||
}
|
||||
throw;
|
||||
}
|
||||
return m_snapshots.back();
|
||||
}
|
||||
|
||||
|
@ -551,6 +574,32 @@ SnapshotDB& SnapshotDB::singleton()
|
|||
return instance;
|
||||
}
|
||||
|
||||
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
|
||||
{
|
||||
try {
|
||||
return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
|
||||
} catch (std::exception &err) {
|
||||
show_error(static_cast<wxWindow*>(wxGetApp().mainframe),
|
||||
_L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message)
|
||||
{
|
||||
try {
|
||||
SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
|
||||
return true;
|
||||
} catch (std::exception &err) {
|
||||
wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
|
||||
_L("PrusaSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
|
||||
_L("PrusaSlicer error"),
|
||||
wxYES_NO);
|
||||
dlg.SetYesNoLabels(_L("Continue"), _L("Abort"));
|
||||
return dlg.ShowModal() == wxID_YES;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -127,6 +127,13 @@ private:
|
|||
std::vector<Snapshot> m_snapshots;
|
||||
};
|
||||
|
||||
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report an error and return nullptr.
|
||||
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
|
||||
|
||||
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report "message", and present a "Continue" or "Abort" buttons to respond.
|
||||
// Return true on success and on "Continue" to continue with the process (for example installation of presets).
|
||||
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message);
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -303,13 +303,16 @@ void GLVolume::SinkingContours::render()
|
|||
|
||||
void GLVolume::SinkingContours::update()
|
||||
{
|
||||
if (m_parent.is_sinking() && !m_parent.is_below_printbed()) {
|
||||
int object_idx = m_parent.object_idx();
|
||||
Model& model = GUI::wxGetApp().plater()->model();
|
||||
|
||||
if (0 <= object_idx && object_idx < (int)model.objects.size() && m_parent.is_sinking() && !m_parent.is_below_printbed()) {
|
||||
const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box();
|
||||
if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) {
|
||||
m_old_box = box;
|
||||
m_shift = Vec3d::Zero();
|
||||
|
||||
const TriangleMesh& mesh = GUI::wxGetApp().plater()->model().objects[m_parent.object_idx()]->volumes[m_parent.volume_idx()]->mesh();
|
||||
const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh();
|
||||
assert(mesh.has_shared_vertices());
|
||||
|
||||
m_model.reset();
|
||||
|
@ -592,7 +595,7 @@ bool GLVolume::is_sinking() const
|
|||
|
||||
bool GLVolume::is_below_printbed() const
|
||||
{
|
||||
return transformed_convex_hull_bounding_box().max(2) < 0.0;
|
||||
return transformed_convex_hull_bounding_box().max.z() < 0.0;
|
||||
}
|
||||
|
||||
#if ENABLE_SINKING_CONTOURS
|
||||
|
@ -843,12 +846,13 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
volume.first->set_render_color();
|
||||
|
||||
// render sinking contours of non-hovered volumes
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
|
||||
shader->stop_using();
|
||||
volume.first->render_sinking_contours();
|
||||
shader->start_using();
|
||||
}
|
||||
if (m_show_sinking_contours)
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
|
||||
shader->stop_using();
|
||||
volume.first->render_sinking_contours();
|
||||
shader->start_using();
|
||||
}
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
@ -887,17 +891,18 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
||||
}
|
||||
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
// render sinking contours of hovered/displaced volumes
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
(volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) {
|
||||
shader->stop_using();
|
||||
glsafe(::glDepthFunc(GL_ALWAYS));
|
||||
volume.first->render_sinking_contours();
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
shader->start_using();
|
||||
if (m_show_sinking_contours)
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
// render sinking contours of hovered/displaced volumes
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
(volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) {
|
||||
shader->stop_using();
|
||||
glsafe(::glDepthFunc(GL_ALWAYS));
|
||||
volume.first->render_sinking_contours();
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
shader->start_using();
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
|
|
@ -539,6 +539,7 @@ private:
|
|||
};
|
||||
|
||||
Slope m_slope;
|
||||
bool m_show_sinking_contours = false;
|
||||
|
||||
public:
|
||||
GLVolumePtrs volumes;
|
||||
|
@ -608,6 +609,7 @@ public:
|
|||
float get_slope_normal_z() const { return m_slope.normal_z; }
|
||||
void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
|
||||
void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
|
||||
void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
|
||||
|
||||
// returns true if all the volumes are completely contained in the print volume
|
||||
// returns the containment state in the given out_state, if non-null
|
||||
|
|
|
@ -195,7 +195,7 @@ void CopyrightsDialog::on_dpi_changed(const wxRect &suggested_rect)
|
|||
|
||||
void CopyrightsDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
{
|
||||
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
|
||||
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
|
||||
event.Skip(false);
|
||||
}
|
||||
|
||||
|
@ -344,7 +344,7 @@ void AboutDialog::on_dpi_changed(const wxRect &suggested_rect)
|
|||
|
||||
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
{
|
||||
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
|
||||
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
|
||||
event.Skip(false);
|
||||
}
|
||||
|
||||
|
|
|
@ -154,54 +154,7 @@ void BackgroundSlicingProcess::process_fff()
|
|||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
|
||||
// let the gcode window to unmap the temporary .gcode file (m_temp_output_path)
|
||||
// because the scripts may want to modify it
|
||||
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
|
||||
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
run_post_process_scripts(m_temp_output_path, m_fff_print->full_print_config());
|
||||
|
||||
// let the gcode window to reload and remap the temporary .gcode file (m_temp_output_path)
|
||||
GUI::wxGetApp().plater()->start_mapping_gcode_window();
|
||||
|
||||
//FIXME localize the messages
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
std::string error_message;
|
||||
int copy_ret_val = CopyFileResult::SUCCESS;
|
||||
try
|
||||
{
|
||||
copy_ret_val = copy_file(m_temp_output_path, export_path, error_message, m_export_path_on_removable_media);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
}
|
||||
switch (copy_ret_val) {
|
||||
case CopyFileResult::SUCCESS: break; // no error
|
||||
case CopyFileResult::FAIL_COPY_FILE:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_FILES_DIFFERENT:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_RENAMING:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
default:
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
|
||||
finalize_gcode();
|
||||
} else if (! m_upload_job.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
prepare_upload();
|
||||
|
@ -621,8 +574,11 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
|
|||
// Some FFF status was invalidated, and the G-code was not exported yet.
|
||||
// Let the G-code preview UI know that the final G-code preview is not valid.
|
||||
// In addition, this early memory deallocation reduces memory footprint.
|
||||
if (m_gcode_result != nullptr)
|
||||
if (m_gcode_result != nullptr) {
|
||||
//FIXME calling platter from here is not a staple of a good architecture.
|
||||
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
|
||||
m_gcode_result->reset();
|
||||
}
|
||||
}
|
||||
return invalidated;
|
||||
}
|
||||
|
@ -698,12 +654,73 @@ bool BackgroundSlicingProcess::invalidate_all_steps()
|
|||
return m_step_state.invalidate_all([this](){ this->stop_internal(); });
|
||||
}
|
||||
|
||||
// G-code is generated in m_temp_output_path.
|
||||
// Optionally run a post-processing script on a copy of m_temp_output_path.
|
||||
// Copy the final G-code to target location (possibly a SD card, if it is a removable media, then verify that the file was written without an error).
|
||||
void BackgroundSlicingProcess::finalize_gcode()
|
||||
{
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
std::string output_path = m_temp_output_path;
|
||||
// Both output_path and export_path ar in-out parameters.
|
||||
// If post processed, output_path will differ from m_temp_output_path as run_post_process_scripts() will make a copy of the G-code to not
|
||||
// collide with the G-code viewer memory mapping of the unprocessed G-code. G-code viewer maps unprocessed G-code, because m_gcode_result
|
||||
// is calculated for the unprocessed G-code and it references lines in the memory mapped G-code file by line numbers.
|
||||
// export_path may be changed by the post-processing script as well if the post processing script decides so, see GH #6042.
|
||||
bool post_processed = run_post_process_scripts(output_path, true, "File", export_path, m_fff_print->full_print_config());
|
||||
auto remove_post_processed_temp_file = [post_processed, &output_path]() {
|
||||
if (post_processed)
|
||||
try {
|
||||
boost::filesystem::remove(output_path);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to remove temp file " << output_path << ": " << ex.what();
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME localize the messages
|
||||
std::string error_message;
|
||||
int copy_ret_val = CopyFileResult::SUCCESS;
|
||||
try
|
||||
{
|
||||
copy_ret_val = copy_file(output_path, export_path, error_message, m_export_path_on_removable_media);
|
||||
remove_post_processed_temp_file();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
remove_post_processed_temp_file();
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
}
|
||||
switch (copy_ret_val) {
|
||||
case CopyFileResult::SUCCESS: break; // no error
|
||||
case CopyFileResult::FAIL_COPY_FILE:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_FILES_DIFFERENT:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_RENAMING:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % output_path % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
default:
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
|
||||
}
|
||||
|
||||
// A print host upload job has been scheduled, enqueue it to the printhost job queue
|
||||
void BackgroundSlicingProcess::prepare_upload()
|
||||
{
|
||||
// A print host upload job has been scheduled, enqueue it to the printhost job queue
|
||||
|
||||
// XXX: is fs::path::string() right?
|
||||
|
||||
// Generate a unique temp path to which the gcode/zip file is copied/exported
|
||||
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%");
|
||||
|
@ -711,11 +728,15 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
if (m_print == m_fff_print) {
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
std::string error_message;
|
||||
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) {
|
||||
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
|
||||
}
|
||||
run_post_process_scripts(source_path.string(), m_fff_print->full_print_config());
|
||||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
// Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
|
||||
// (not here, but when the final target is a file).
|
||||
std::string source_path_str = source_path.string();
|
||||
std::string output_name_str = m_upload_job.upload_data.upload_path.string();
|
||||
if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config()))
|
||||
m_upload_job.upload_data.upload_path = output_name_str;
|
||||
} else {
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
|
||||
|
|
|
@ -216,9 +216,9 @@ private:
|
|||
Print *m_fff_print = nullptr;
|
||||
SLAPrint *m_sla_print = nullptr;
|
||||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodeProcessor::Result *m_gcode_result = nullptr;
|
||||
GCodeProcessor::Result *m_gcode_result = nullptr;
|
||||
// Callback function, used to write thumbnails into gcode.
|
||||
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
|
||||
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;
|
||||
|
@ -262,6 +262,7 @@ private:
|
|||
bool invalidate_all_steps();
|
||||
// If the background processing stop was requested, throw CanceledException.
|
||||
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
|
||||
void finalize_gcode();
|
||||
void prepare_upload();
|
||||
// To be executed at the background thread.
|
||||
ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms);
|
||||
|
|
|
@ -12,6 +12,46 @@
|
|||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void ButtonsDescription::FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour)
|
||||
{
|
||||
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 5, 5);
|
||||
sizer->Add(grid_sizer, 0, wxEXPAND);
|
||||
|
||||
ScalableBitmap bmp_delete = ScalableBitmap(parent, "cross");
|
||||
ScalableBitmap bmp_delete_focus = ScalableBitmap(parent, "cross_focus");
|
||||
|
||||
auto add_color = [grid_sizer, parent](wxColourPickerCtrl** color_picker, const wxColour& color, const wxColour& def_color, wxString label_text) {
|
||||
//
|
||||
auto sys_label = new wxStaticText(parent, wxID_ANY, label_text);
|
||||
sys_label->SetForegroundColour(color);
|
||||
|
||||
*color_picker = new wxColourPickerCtrl(parent, wxID_ANY, color);
|
||||
wxGetApp().UpdateDarkUI((*color_picker)->GetPickerCtrl(), true);
|
||||
(*color_picker)->Bind(wxEVT_COLOURPICKER_CHANGED, [color_picker, sys_label](wxCommandEvent&) {
|
||||
sys_label->SetForegroundColour((*color_picker)->GetColour());
|
||||
sys_label->Refresh();
|
||||
});
|
||||
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, "undo");
|
||||
btn->SetToolTip(_L("Revert color to default"));
|
||||
btn->Bind(wxEVT_BUTTON, [sys_label, color_picker, def_color](wxEvent& event) {
|
||||
(*color_picker)->SetColour(def_color);
|
||||
sys_label->SetForegroundColour(def_color);
|
||||
sys_label->Refresh();
|
||||
});
|
||||
parent->Bind(wxEVT_UPDATE_UI, [color_picker, def_color](wxUpdateUIEvent& evt) {
|
||||
evt.Enable((*color_picker)->GetColour() != def_color);
|
||||
}, btn->GetId());
|
||||
|
||||
grid_sizer->Add(*color_picker, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(btn, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(sys_label, 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
||||
};
|
||||
|
||||
add_color(sys_colour, wxGetApp().get_label_clr_sys(), wxGetApp().get_label_default_clr_system(), _L("Value is the same as the system value"));
|
||||
add_color(mod_colour, wxGetApp().get_label_clr_modified(),wxGetApp().get_label_default_clr_modified(), _L("Value was changed and is not equal to the system value or the last saved preset"));
|
||||
}
|
||||
|
||||
ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry> &entries) :
|
||||
wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize),
|
||||
m_entries(entries)
|
||||
|
@ -35,50 +75,23 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry
|
|||
}
|
||||
|
||||
// Text color description
|
||||
auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value")));
|
||||
sys_label->SetForegroundColour(wxGetApp().get_label_clr_sys());
|
||||
auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_sys());
|
||||
wxGetApp().UpdateDarkUI(sys_colour->GetPickerCtrl(), true);
|
||||
sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e)
|
||||
{
|
||||
sys_label->SetForegroundColour(sys_colour->GetColour());
|
||||
sys_label->Refresh();
|
||||
}));
|
||||
size_t t= 0;
|
||||
while (t < 3) {
|
||||
grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
||||
++t;
|
||||
}
|
||||
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(sys_colour, -1, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
||||
|
||||
auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset")));
|
||||
mod_label->SetForegroundColour(wxGetApp().get_label_clr_modified());
|
||||
auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_modified());
|
||||
wxGetApp().UpdateDarkUI(mod_colour->GetPickerCtrl(), true);
|
||||
mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e)
|
||||
{
|
||||
mod_label->SetForegroundColour(mod_colour->GetColour());
|
||||
mod_label->Refresh();
|
||||
}));
|
||||
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(mod_colour, -1, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(mod_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
||||
|
||||
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
FillSizerWithTextColorDescriptions(sizer, this, &sys_colour, &mod_colour);
|
||||
main_sizer->Add(sizer, 0, wxEXPAND | wxALL, 20);
|
||||
|
||||
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
|
||||
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) {
|
||||
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
wxGetApp().set_label_clr_sys(sys_colour->GetColour());
|
||||
wxGetApp().set_label_clr_modified(mod_colour->GetColour());
|
||||
EndModal(wxID_OK);
|
||||
});
|
||||
|
||||
wxGetApp().UpdateDarkUI(btn);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(wxID_CANCEL, this)));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
}
|
||||
|
|
|
@ -5,12 +5,15 @@
|
|||
#include <vector>
|
||||
|
||||
class ScalableBitmap;
|
||||
class wxColourPickerCtrl;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class ButtonsDescription : public wxDialog
|
||||
{
|
||||
wxColourPickerCtrl* sys_colour{ nullptr };
|
||||
wxColourPickerCtrl* mod_colour{ nullptr };
|
||||
public:
|
||||
struct Entry {
|
||||
Entry(ScalableBitmap *bitmap, const std::string &symbol, const std::string &explanation) : bitmap(bitmap), symbol(symbol), explanation(explanation) {}
|
||||
|
@ -23,6 +26,8 @@ public:
|
|||
ButtonsDescription(wxWindow* parent, const std::vector<Entry> &entries);
|
||||
~ButtonsDescription() {}
|
||||
|
||||
static void FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour);
|
||||
|
||||
private:
|
||||
std::vector<Entry> m_entries;
|
||||
};
|
||||
|
|
|
@ -268,7 +268,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
|||
toggle_field("gap_fill_speed", have_perimeters);
|
||||
|
||||
for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" })
|
||||
toggle_field(el, has_top_solid_infill);
|
||||
toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill));
|
||||
|
||||
bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
|
||||
for (auto el : { "perimeter_acceleration", "infill_acceleration",
|
||||
|
|
|
@ -65,7 +65,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
|
|||
this->is_prusa_bundle = ais_prusa_bundle;
|
||||
|
||||
std::string path_string = source_path.string();
|
||||
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem);
|
||||
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
|
||||
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
|
||||
path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
UNUSED(config_substitutions);
|
||||
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
|
||||
assert(config_substitutions.empty());
|
||||
|
@ -492,15 +494,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent)
|
|||
{
|
||||
welcome_text->Hide();
|
||||
cbox_reset->Hide();
|
||||
#ifdef __linux__
|
||||
if (!DesktopIntegrationDialog::is_integrated())
|
||||
cbox_integrate->Show(true);
|
||||
else
|
||||
cbox_integrate->Hide();
|
||||
#else
|
||||
cbox_integrate->Hide();
|
||||
#endif
|
||||
|
||||
cbox_integrate->Hide();
|
||||
}
|
||||
|
||||
void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
|
||||
|
@ -508,7 +502,7 @@ void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
|
|||
const bool data_empty = run_reason == ConfigWizard::RR_DATA_EMPTY;
|
||||
welcome_text->Show(data_empty);
|
||||
cbox_reset->Show(!data_empty);
|
||||
#ifdef __linux__
|
||||
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
if (!DesktopIntegrationDialog::is_integrated())
|
||||
cbox_integrate->Show(true);
|
||||
else
|
||||
|
@ -2451,7 +2445,35 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
|
|||
return true;
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
static std::set<std::string> get_new_added_presets(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data)
|
||||
{
|
||||
auto get_aliases = [](const std::map<std::string, std::string>& data) {
|
||||
std::set<std::string> old_aliases;
|
||||
for (auto item : data) {
|
||||
const std::string& name = item.first;
|
||||
size_t pos = name.find("@");
|
||||
old_aliases.emplace(pos == std::string::npos ? name : name.substr(0, pos-1));
|
||||
}
|
||||
return old_aliases;
|
||||
};
|
||||
|
||||
std::set<std::string> old_aliases = get_aliases(old_data);
|
||||
std::set<std::string> new_aliases = get_aliases(new_data);
|
||||
std::set<std::string> diff;
|
||||
std::set_difference(new_aliases.begin(), new_aliases.end(), old_aliases.begin(), old_aliases.end(), std::inserter(diff, diff.begin()));
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
static std::string get_first_added_preset(const std::map<std::string, std::string>& old_data, const std::map<std::string, std::string>& new_data)
|
||||
{
|
||||
std::set<std::string> diff = get_new_added_presets(old_data, new_data);
|
||||
if (diff.empty())
|
||||
return std::string();
|
||||
return *diff.begin();
|
||||
}
|
||||
|
||||
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
{
|
||||
const auto enabled_vendors = appconfig_new.vendors();
|
||||
|
||||
|
@ -2506,14 +2528,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
break;
|
||||
}
|
||||
|
||||
if (snapshot) {
|
||||
SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason);
|
||||
}
|
||||
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
|
||||
return false;
|
||||
|
||||
if (install_bundles.size() > 0) {
|
||||
// Install bundles from resources.
|
||||
// Don't create snapshot - we've already done that above if applicable.
|
||||
updater->install_bundles_rsrc(std::move(install_bundles), false);
|
||||
if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
|
||||
return false;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
|
||||
}
|
||||
|
@ -2523,13 +2545,61 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
preset_bundle->reset(true);
|
||||
}
|
||||
|
||||
std::string preferred_model;
|
||||
std::string preferred_variant;
|
||||
const auto enabled_vendors_old = app_config->vendors();
|
||||
auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
|
||||
const auto config = enabled_vendors.find(bundle_name);
|
||||
if (config == enabled_vendors.end())
|
||||
return std::string();
|
||||
for (const auto& model : bundle.vendor_profile->models) {
|
||||
if (const auto model_it = config->second.find(model.id);
|
||||
model_it != config->second.end() && model_it->second.size() > 0) {
|
||||
variant = *model_it->second.begin();
|
||||
const auto config_old = enabled_vendors_old.find(bundle_name);
|
||||
if (config_old == enabled_vendors_old.end())
|
||||
return model.id;
|
||||
const auto model_it_old = config_old->second.find(model.id);
|
||||
if (model_it_old == config_old->second.end())
|
||||
return model.id;
|
||||
else if (model_it_old->second != model_it->second) {
|
||||
for (const auto& var : model_it->second)
|
||||
if (model_it_old->second.find(var) == model_it_old->second.end()) {
|
||||
variant = var;
|
||||
return model.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!variant.empty())
|
||||
variant.clear();
|
||||
return std::string();
|
||||
};
|
||||
// Prusa printers are considered first, then 3rd party.
|
||||
if (preferred_model = get_preferred_printer_model("PrusaResearch", bundles.prusa_bundle(), preferred_variant);
|
||||
preferred_model.empty()) {
|
||||
for (const auto& bundle : bundles) {
|
||||
if (bundle.second.is_prusa_bundle) { continue; }
|
||||
if (preferred_model = get_preferred_printer_model(bundle.first, bundle.second, preferred_variant);
|
||||
!preferred_model.empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string first_added_filament, first_added_sla_material;
|
||||
auto apply_section = [this, app_config](const std::string& section_name, std::string& first_added_preset) {
|
||||
if (appconfig_new.has_section(section_name)) {
|
||||
// get first of new added preset names
|
||||
const std::map<std::string, std::string>& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>();
|
||||
first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name));
|
||||
app_config->set_section(section_name, appconfig_new.get_section(section_name));
|
||||
}
|
||||
};
|
||||
apply_section(AppConfig::SECTION_FILAMENTS, first_added_filament);
|
||||
apply_section(AppConfig::SECTION_MATERIALS, first_added_sla_material);
|
||||
|
||||
app_config->set_vendors(appconfig_new);
|
||||
if (appconfig_new.has_section(AppConfig::SECTION_FILAMENTS)) {
|
||||
app_config->set_section(AppConfig::SECTION_FILAMENTS, appconfig_new.get_section(AppConfig::SECTION_FILAMENTS));
|
||||
}
|
||||
if (appconfig_new.has_section(AppConfig::SECTION_MATERIALS)) {
|
||||
app_config->set_section(AppConfig::SECTION_MATERIALS, appconfig_new.get_section(AppConfig::SECTION_MATERIALS));
|
||||
}
|
||||
|
||||
app_config->set("version_check", page_update->version_check ? "1" : "0");
|
||||
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
|
||||
app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
|
||||
|
@ -2554,43 +2624,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
|
||||
page_mode->serialize_mode(app_config);
|
||||
|
||||
std::string preferred_model;
|
||||
|
||||
// Figure out the default pre-selected printer based on the selections in the pickers.
|
||||
// The default is the first selected printer model (one with at least 1 variant selected).
|
||||
// The default is only applied by load_presets() if the user doesn't have a (visible) printer
|
||||
// selected already.
|
||||
// Prusa printers are considered first, then 3rd party.
|
||||
const auto config_prusa = enabled_vendors.find("PrusaResearch");
|
||||
if (config_prusa != enabled_vendors.end()) {
|
||||
for (const auto &model : bundles.prusa_bundle().vendor_profile->models) {
|
||||
const auto model_it = config_prusa->second.find(model.id);
|
||||
if (model_it != config_prusa->second.end() && model_it->second.size() > 0) {
|
||||
preferred_model = model.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (preferred_model.empty()) {
|
||||
for (const auto &bundle : bundles) {
|
||||
if (bundle.second.is_prusa_bundle) { continue; }
|
||||
|
||||
const auto config = enabled_vendors.find(bundle.first);
|
||||
if (config == enabled_vendors.end()) { continue; }
|
||||
for (const auto &model : bundle.second.vendor_profile->models) {
|
||||
const auto model_it = config->second.find(model.id);
|
||||
if (model_it != config->second.end() && model_it->second.size() > 0) {
|
||||
preferred_model = model.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reloading the configs after some modifications were done to PrusaSlicer.ini.
|
||||
// Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
|
||||
// and the Wizard shall not create any new values that would require substitution.
|
||||
PresetsConfigSubstitutions substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, preferred_model);
|
||||
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem,
|
||||
{preferred_model, preferred_variant, first_added_filament, first_added_sla_material});
|
||||
|
||||
if (page_custom->custom_wanted()) {
|
||||
page_firmware->apply_custom_config(*custom_config);
|
||||
|
@ -2604,6 +2639,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
|
||||
// Update the selections from the compatibilty.
|
||||
preset_bundle->export_selections(*app_config);
|
||||
|
||||
return true;
|
||||
}
|
||||
void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
|
||||
{
|
||||
|
@ -2814,7 +2851,8 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
|
|||
p->set_start_page(start_page);
|
||||
|
||||
if (ShowModal() == wxID_OK) {
|
||||
p->apply_config(app.app_config, app.preset_bundle, app.preset_updater);
|
||||
if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater))
|
||||
return false;
|
||||
app.app_config->set_legacy_datadir(false);
|
||||
app.update_mode();
|
||||
app.obj_manipul()->update_ui_from_settings();
|
||||
|
|
|
@ -611,7 +611,7 @@ struct ConfigWizard::priv
|
|||
|
||||
bool on_bnt_finish();
|
||||
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
|
||||
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
// #ys_FIXME_alise
|
||||
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
|
||||
#ifdef __linux__
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
#ifdef __linux__
|
||||
#include "DesktopIntegrationDialog.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/dll/runtime_symbol_info.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <wx/stattext.h>
|
||||
|
@ -17,9 +21,9 @@
|
|||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace integrate_desktop_internal{
|
||||
namespace {
|
||||
// Disects path strings stored in system variable divided by ':' and adds into vector
|
||||
static void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
|
||||
void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
|
||||
{
|
||||
wxString wxdirs;
|
||||
if (! wxGetEnv(boost::nowide::widen(var), &wxdirs) || wxdirs.empty() )
|
||||
|
@ -34,7 +38,7 @@ static void resolve_path_from_var(const std::string& var, std::vector<std::strin
|
|||
paths.push_back(dirs);
|
||||
}
|
||||
// Return true if directory in path p+dir_name exists
|
||||
static bool contains_path_dir(const std::string& p, const std::string& dir_name)
|
||||
bool contains_path_dir(const std::string& p, const std::string& dir_name)
|
||||
{
|
||||
if (p.empty() || dir_name.empty())
|
||||
return false;
|
||||
|
@ -47,7 +51,7 @@ static bool contains_path_dir(const std::string& p, const std::string& dir_name)
|
|||
return false;
|
||||
}
|
||||
// Creates directory in path if not exists yet
|
||||
static void create_dir(const boost::filesystem::path& path)
|
||||
void create_dir(const boost::filesystem::path& path)
|
||||
{
|
||||
if (boost::filesystem::exists(path))
|
||||
return;
|
||||
|
@ -58,7 +62,7 @@ static void create_dir(const boost::filesystem::path& path)
|
|||
BOOST_LOG_TRIVIAL(error)<< "create directory failed: " << ec.message();
|
||||
}
|
||||
// Starts at basic_path (excluded) and creates all directories in dir_path
|
||||
static void create_path(const std::string& basic_path, const std::string& dir_path)
|
||||
void create_path(const std::string& basic_path, const std::string& dir_path)
|
||||
{
|
||||
if (basic_path.empty() || dir_path.empty())
|
||||
return;
|
||||
|
@ -76,7 +80,7 @@ static void create_path(const std::string& basic_path, const std::string& dir_pa
|
|||
create_dir(path);
|
||||
}
|
||||
// Calls our internal copy_file function to copy file at icon_path to dest_path
|
||||
static bool copy_icon(const std::string& icon_path, const std::string& dest_path)
|
||||
bool copy_icon(const std::string& icon_path, const std::string& dest_path)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) <<"icon from "<< icon_path;
|
||||
BOOST_LOG_TRIVIAL(debug) <<"icon to "<< dest_path;
|
||||
|
@ -90,8 +94,8 @@ static bool copy_icon(const std::string& icon_path, const std::string& dest_path
|
|||
return true;
|
||||
}
|
||||
// Creates new file filled with data.
|
||||
static bool create_desktop_file(const std::string& path, const std::string& data)
|
||||
{
|
||||
bool create_desktop_file(const std::string& path, const std::string& data)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) <<".desktop to "<< path;
|
||||
std::ofstream output(path);
|
||||
output << data;
|
||||
|
@ -109,10 +113,6 @@ static bool create_desktop_file(const std::string& path, const std::string& data
|
|||
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
|
||||
bool DesktopIntegrationDialog::is_integrated()
|
||||
{
|
||||
const char *appimage_env = std::getenv("APPIMAGE");
|
||||
if (!appimage_env)
|
||||
return false;
|
||||
|
||||
const AppConfig *app_config = wxGetApp().app_config;
|
||||
std::string path(app_config->get("desktop_integration_app_path"));
|
||||
BOOST_LOG_TRIVIAL(debug) << "Desktop integration desktop file path: " << path;
|
||||
|
@ -126,10 +126,6 @@ bool DesktopIntegrationDialog::is_integrated()
|
|||
}
|
||||
bool DesktopIntegrationDialog::integration_possible()
|
||||
{
|
||||
|
||||
const char *appimage_env = std::getenv("APPIMAGE");
|
||||
if (!appimage_env)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
void DesktopIntegrationDialog::perform_desktop_integration()
|
||||
|
@ -138,19 +134,31 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
|
||||
// Path to appimage
|
||||
const char *appimage_env = std::getenv("APPIMAGE");
|
||||
std::string appimage_path;
|
||||
std::string excutable_path;
|
||||
if (appimage_env) {
|
||||
try {
|
||||
appimage_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
|
||||
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
|
||||
} catch (std::exception &) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - boost::filesystem::canonical did not return appimage path.";
|
||||
show_error(nullptr, _L("Performing desktop integration failed - boost::filesystem::canonical did not return appimage path."));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// not appimage - not performing
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - not Appimage executable.";
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
|
||||
return;
|
||||
// not appimage - find executable
|
||||
excutable_path = boost::dll::program_location().string();
|
||||
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
|
||||
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
|
||||
if (excutable_path.empty())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - no executable found.";
|
||||
show_error(nullptr, _L("Performing desktop integration failed - Could not find executable."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
|
||||
boost::replace_all(excutable_path, "'", "'\\''");
|
||||
|
||||
// Find directories icons and applications
|
||||
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
|
||||
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
|
||||
|
@ -158,8 +166,8 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
|
||||
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
|
||||
std::vector<std::string>target_candidates;
|
||||
integrate_desktop_internal::resolve_path_from_var("XDG_DATA_HOME", target_candidates);
|
||||
integrate_desktop_internal::resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
|
||||
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
|
||||
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
|
||||
|
||||
AppConfig *app_config = wxGetApp().app_config;
|
||||
// suffix string to create different desktop file for alpha, beta.
|
||||
|
@ -186,7 +194,6 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
icon_theme_dirs = "/hicolor/96x96/apps";
|
||||
}
|
||||
|
||||
|
||||
std::string target_dir_icons;
|
||||
std::string target_dir_desktop;
|
||||
|
||||
|
@ -194,24 +201,24 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
// iterate thru target_candidates to find icons folder
|
||||
for (size_t i = 0; i < target_candidates.size(); ++i) {
|
||||
// Copy icon PrusaSlicer.png from resources_dir()/icons to target_dir_icons/icons/
|
||||
if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "icons")) {
|
||||
if (contains_path_dir(target_candidates[i], "icons")) {
|
||||
target_dir_icons = target_candidates[i];
|
||||
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
|
||||
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
|
||||
if (integrate_desktop_internal::copy_icon(icon_path, dest_path))
|
||||
if (copy_icon(icon_path, dest_path))
|
||||
break; // success
|
||||
else
|
||||
target_dir_icons.clear(); // copying failed
|
||||
// if all failed - try creating default home folder
|
||||
if (i == target_candidates.size() - 1) {
|
||||
// create $HOME/.local/share
|
||||
integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
|
||||
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
|
||||
// copy icon
|
||||
target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
|
||||
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer.png",resources_dir());
|
||||
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
|
||||
if (!integrate_desktop_internal::contains_path_dir(target_dir_icons, "icons")
|
||||
|| !integrate_desktop_internal::copy_icon(icon_path, dest_path)) {
|
||||
if (!contains_path_dir(target_dir_icons, "icons")
|
||||
|| !copy_icon(icon_path, dest_path)) {
|
||||
// every attempt failed - icon wont be present
|
||||
target_dir_icons.clear();
|
||||
}
|
||||
|
@ -228,7 +235,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
// iterate thru target_candidates to find applications folder
|
||||
for (size_t i = 0; i < target_candidates.size(); ++i)
|
||||
{
|
||||
if (integrate_desktop_internal::contains_path_dir(target_candidates[i], "applications")) {
|
||||
if (contains_path_dir(target_candidates[i], "applications")) {
|
||||
target_dir_desktop = target_candidates[i];
|
||||
// Write slicer desktop file
|
||||
std::string desktop_file = GUI::format(
|
||||
|
@ -236,33 +243,33 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
"Name=PrusaSlicer%1%\n"
|
||||
"GenericName=3D Printing Software\n"
|
||||
"Icon=PrusaSlicer%2%\n"
|
||||
"Exec=%3% %%F\n"
|
||||
"Exec=\'%3%\' %%F\n"
|
||||
"Terminal=false\n"
|
||||
"Type=Application\n"
|
||||
"MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
|
||||
"Categories=Graphics;3DGraphics;Engineering;\n"
|
||||
"Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
|
||||
"StartupNotify=false\n"
|
||||
"StartupWMClass=prusa-slicer", name_suffix, version_suffix, appimage_path);
|
||||
"StartupWMClass=prusa-slicer", name_suffix, version_suffix, excutable_path);
|
||||
|
||||
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (integrate_desktop_internal::create_desktop_file(path, desktop_file)){
|
||||
if (create_desktop_file(path, desktop_file)){
|
||||
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicer.desktop file installation success.";
|
||||
break;
|
||||
} else {
|
||||
// write failed - try another path
|
||||
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer.desktop file installation failed.";
|
||||
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicer.desktop file installation failed. failed path: " << target_candidates[i];
|
||||
target_dir_desktop.clear();
|
||||
}
|
||||
// if all failed - try creating default home folder
|
||||
if (i == target_candidates.size() - 1) {
|
||||
// create $HOME/.local/share
|
||||
integrate_desktop_internal::create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
|
||||
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
|
||||
// create desktop file
|
||||
target_dir_desktop = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
|
||||
std::string path = GUI::format("%1%/applications/PrusaSlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (integrate_desktop_internal::contains_path_dir(target_dir_desktop, "applications")) {
|
||||
if (!integrate_desktop_internal::create_desktop_file(path, desktop_file)) {
|
||||
if (contains_path_dir(target_dir_desktop, "applications")) {
|
||||
if (!create_desktop_file(path, desktop_file)) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file";
|
||||
return;
|
||||
|
@ -278,7 +285,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
if(target_dir_desktop.empty()) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not find applications directory";
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
|
||||
show_error(nullptr, _L("Performing desktop integration failed - could not find applications directory."));
|
||||
return;
|
||||
}
|
||||
// save path to desktop file
|
||||
|
@ -290,7 +297,7 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
{
|
||||
std::string icon_path = GUI::format("%1%/icons/PrusaSlicer-gcodeviewer_192px.png",resources_dir());
|
||||
std::string dest_path = GUI::format("%1%/icons/%2%PrusaSlicer-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
|
||||
if (integrate_desktop_internal::copy_icon(icon_path, dest_path))
|
||||
if (copy_icon(icon_path, dest_path))
|
||||
// save path to icon
|
||||
app_config->set("desktop_integration_icon_viewer_path", dest_path);
|
||||
else
|
||||
|
@ -303,32 +310,26 @@ void DesktopIntegrationDialog::perform_desktop_integration()
|
|||
"Name=Prusa Gcode Viewer%1%\n"
|
||||
"GenericName=3D Printing Software\n"
|
||||
"Icon=PrusaSlicer-gcodeviewer%2%\n"
|
||||
"Exec=%3% --gcodeviwer %%F\n"
|
||||
"Exec=\'%3%\' --gcodeviwer %%F\n"
|
||||
"Terminal=false\n"
|
||||
"Type=Application\n"
|
||||
"MimeType=text/x.gcode;\n"
|
||||
"Categories=Graphics;3DGraphics;\n"
|
||||
"Keywords=3D;Printing;Slicer;\n"
|
||||
"StartupNotify=false", name_suffix, version_suffix, appimage_path);
|
||||
"StartupNotify=false", name_suffix, version_suffix, excutable_path);
|
||||
|
||||
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (integrate_desktop_internal::create_desktop_file(desktop_path, desktop_file))
|
||||
if (create_desktop_file(desktop_path, desktop_file))
|
||||
// save path to desktop file
|
||||
app_config->set("desktop_integration_app_viewer_path", desktop_path);
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could create gcode viewer desktop file";
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationFail);
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file";
|
||||
show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. PrusaSlicer desktop file was probably created successfully."));
|
||||
}
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
|
||||
}
|
||||
void DesktopIntegrationDialog::undo_desktop_intgration()
|
||||
{
|
||||
const char *appimage_env = std::getenv("APPIMAGE");
|
||||
if (!appimage_env) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Undo desktop integration failed - not Appimage executable.";
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationFail);
|
||||
return;
|
||||
}
|
||||
const AppConfig *app_config = wxGetApp().app_config;
|
||||
// slicer .desktop
|
||||
std::string path = std::string(app_config->get("desktop_integration_app_path"));
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "GUI_Utils.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/dialog.h>
|
||||
|
@ -383,7 +384,11 @@ void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
|
|||
// Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
|
||||
post_ticks_changed_event();
|
||||
|
||||
if (custom_gcode_per_print_z.mode)
|
||||
// init extruder sequence in respect to the extruders count
|
||||
if (m_ticks.empty())
|
||||
m_extruders_sequence.init(m_extruder_colors.size());
|
||||
|
||||
if (custom_gcode_per_print_z.mode && !custom_gcode_per_print_z.gcodes.empty())
|
||||
m_ticks.mode = custom_gcode_per_print_z.mode;
|
||||
|
||||
Refresh();
|
||||
|
@ -438,7 +443,7 @@ void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, c
|
|||
m_mode = !is_one_extruder_printed_model ? MultiExtruder :
|
||||
only_extruder < 0 ? SingleExtruder :
|
||||
MultiAsSingle;
|
||||
if (!m_ticks.mode)
|
||||
if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode))
|
||||
m_ticks.mode = m_mode;
|
||||
m_only_extruder = only_extruder;
|
||||
|
||||
|
@ -545,7 +550,8 @@ bool Control::is_wipe_tower_layer(int tick) const
|
|||
return false;
|
||||
if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1]))
|
||||
return false;
|
||||
if (m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1])
|
||||
if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) ||
|
||||
(tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@ -1077,7 +1083,9 @@ void Control::draw_ruler(wxDC& dc)
|
|||
{
|
||||
if (m_values.empty())
|
||||
return;
|
||||
m_ruler.update(this->GetParent(), m_values, get_scroll_step());
|
||||
// When "No sparce layer" is enabled, use m_layers_values for ruler update.
|
||||
// Because of m_values has duplicate values in this case.
|
||||
m_ruler.update(this->GetParent(), m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step());
|
||||
|
||||
int height, width;
|
||||
get_size(&width, &height);
|
||||
|
@ -1588,7 +1596,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
|
|||
|
||||
append_submenu(menu, change_extruder_menu, wxID_ANY, change_extruder_menu_name, _L("Use another extruder"),
|
||||
active_extruders[1] > 0 ? "edit_uni" : "change_extruder",
|
||||
[this]() {return m_mode == MultiAsSingle; }, GUI::wxGetApp().plater());
|
||||
[this]() {return m_mode == MultiAsSingle && !GUI::wxGetApp().obj_list()->has_paint_on_segmentation(); }, GUI::wxGetApp().plater());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2020,7 +2028,7 @@ void Control::show_cog_icon_context_menu()
|
|||
append_menu_item(&menu, wxID_ANY, _L("Set extruder sequence for the entire print"), "",
|
||||
[this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu);
|
||||
|
||||
if (m_mode != MultiExtruder && m_draw_mode == dmRegular)
|
||||
if (GUI::wxGetApp().is_editor() && m_mode != MultiExtruder && m_draw_mode == dmRegular)
|
||||
append_menu_item(&menu, wxID_ANY, _L("Set auto color changes"), "",
|
||||
[this](wxCommandEvent&) { auto_color_change(); }, "", &menu);
|
||||
|
||||
|
|
|
@ -180,6 +180,13 @@ struct ExtrudersSequence
|
|||
return;// last item can't be deleted
|
||||
extruders.erase(extruders.begin() + pos);
|
||||
}
|
||||
|
||||
void init(size_t extruders_count)
|
||||
{
|
||||
extruders.clear();
|
||||
for (size_t extruder = 0; extruder < extruders_count; extruder++)
|
||||
extruders.push_back(extruder);
|
||||
}
|
||||
};
|
||||
|
||||
class Control : public wxControl
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "wxExtensions.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "BitmapComboBox.hpp"
|
||||
|
||||
#include <wx/dc.h>
|
||||
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
|
||||
|
@ -296,7 +297,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
|
|||
DataViewBitmapText data;
|
||||
data << value;
|
||||
|
||||
#ifdef _WIN32
|
||||
Slic3r::GUI::BitmapComboBox* c_editor = new Slic3r::GUI::BitmapComboBox(parent, wxID_ANY, wxEmptyString,
|
||||
#else
|
||||
auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString,
|
||||
#endif
|
||||
labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1),
|
||||
0, nullptr , wxCB_READONLY);
|
||||
|
||||
|
@ -310,9 +315,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
|
|||
// to avoid event propagation to other sidebar items
|
||||
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
|
||||
evt.StopPropagation();
|
||||
#ifdef __linux__
|
||||
// FinishEditing grabs new selection and triggers config update. We better call
|
||||
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
|
||||
this->FinishEditing();
|
||||
#endif
|
||||
});
|
||||
|
||||
return c_editor;
|
||||
|
|
|
@ -73,8 +73,6 @@ Field::~Field()
|
|||
{
|
||||
if (m_on_kill_focus)
|
||||
m_on_kill_focus = nullptr;
|
||||
if (m_on_set_focus)
|
||||
m_on_set_focus = nullptr;
|
||||
if (m_on_change)
|
||||
m_on_change = nullptr;
|
||||
if (m_back_to_initial_value)
|
||||
|
@ -156,15 +154,6 @@ void Field::on_kill_focus()
|
|||
m_on_kill_focus(m_opt_id);
|
||||
}
|
||||
|
||||
void Field::on_set_focus(wxEvent& event)
|
||||
{
|
||||
// to allow the default behavior
|
||||
event.Skip();
|
||||
// call the registered function if it is available
|
||||
if (m_on_set_focus!=nullptr)
|
||||
m_on_set_focus(m_opt_id);
|
||||
}
|
||||
|
||||
void Field::on_change_field()
|
||||
{
|
||||
// std::cerr << "calling Field::_on_change \n";
|
||||
|
@ -500,20 +489,18 @@ void TextCtrl::BUILD() {
|
|||
|
||||
temp->SetToolTip(get_tooltip_text(text_value));
|
||||
|
||||
if (style == wxTE_PROCESS_ENTER) {
|
||||
if (style & wxTE_PROCESS_ENTER) {
|
||||
temp->Bind(wxEVT_TEXT_ENTER, ([this, temp](wxEvent& e)
|
||||
{
|
||||
#if !defined(__WXGTK__)
|
||||
e.Skip();
|
||||
temp->GetToolTip()->Enable(true);
|
||||
#endif // __WXGTK__
|
||||
bEnterPressed = true;
|
||||
EnterPressed enter(this);
|
||||
propagate_value();
|
||||
}), temp->GetId());
|
||||
}
|
||||
|
||||
temp->Bind(wxEVT_SET_FOCUS, ([this](wxEvent& e) { on_set_focus(e); }), temp->GetId());
|
||||
|
||||
temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
|
||||
{
|
||||
//! to allow the default handling
|
||||
|
@ -530,26 +517,11 @@ void TextCtrl::BUILD() {
|
|||
temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e)
|
||||
{
|
||||
e.Skip();
|
||||
#ifdef __WXOSX__
|
||||
// OSX issue: For some unknown reason wxEVT_KILL_FOCUS is emitted twice in a row in some cases
|
||||
// (like when information dialog is shown during an update of the option value)
|
||||
// Thus, suppress its second call
|
||||
if (bKilledFocus)
|
||||
return;
|
||||
bKilledFocus = true;
|
||||
#endif // __WXOSX__
|
||||
|
||||
#if !defined(__WXGTK__)
|
||||
temp->GetToolTip()->Enable(true);
|
||||
#endif // __WXGTK__
|
||||
if (bEnterPressed)
|
||||
bEnterPressed = false;
|
||||
else
|
||||
if (!bEnterPressed)
|
||||
propagate_value();
|
||||
#ifdef __WXOSX__
|
||||
// After processing of KILL_FOCUS event we should to invalidate a bKilledFocus flag
|
||||
bKilledFocus = false;
|
||||
#endif // __WXOSX__
|
||||
}), temp->GetId());
|
||||
/*
|
||||
// select all text using Ctrl+A
|
||||
|
@ -851,7 +823,7 @@ void SpinCtrl::BUILD() {
|
|||
bEnterPressed = true;
|
||||
}), temp->GetId());
|
||||
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
|
||||
temp->Bind(wxEVT_TEXT, ([this, temp](wxCommandEvent e)
|
||||
{
|
||||
// # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
|
||||
// # when it was changed from the text control, so the on_change callback
|
||||
|
@ -861,20 +833,28 @@ void SpinCtrl::BUILD() {
|
|||
|
||||
long value;
|
||||
const bool parsed = e.GetString().ToLong(&value);
|
||||
tmp_value = parsed && value >= INT_MIN && value <= INT_MAX ? (int)value : UNDEF_VALUE;
|
||||
|
||||
if (!parsed || value < INT_MIN || value > INT_MAX)
|
||||
tmp_value = UNDEF_VALUE;
|
||||
else {
|
||||
tmp_value = std::min(std::max((int)value, m_opt.min), m_opt.max);
|
||||
#ifdef __WXOSX__
|
||||
// Forcibly set the input value for SpinControl, since the value
|
||||
// inserted from the keyboard or clipboard is not updated under OSX
|
||||
if (tmp_value != UNDEF_VALUE) {
|
||||
wxSpinCtrl* spin = static_cast<wxSpinCtrl*>(window);
|
||||
spin->SetValue(tmp_value);
|
||||
|
||||
// Forcibly set the input value for SpinControl, since the value
|
||||
// inserted from the keyboard or clipboard is not updated under OSX
|
||||
temp->SetValue(tmp_value);
|
||||
// But in SetValue() is executed m_text_ctrl->SelectAll(), so
|
||||
// discard this selection and set insertion point to the end of string
|
||||
spin->GetText()->SetInsertionPointEnd();
|
||||
}
|
||||
temp->GetText()->SetInsertionPointEnd();
|
||||
#else
|
||||
// update value for the control only if it was changed in respect to the Min/max values
|
||||
if (tmp_value != (int)value) {
|
||||
temp->SetValue(tmp_value);
|
||||
// But after SetValue() cursor ison the first position
|
||||
// so put it to the end of string
|
||||
int pos = std::to_string(tmp_value).length();
|
||||
temp->SetSelection(pos, pos);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}), temp->GetId());
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(text_value));
|
||||
|
@ -935,7 +915,7 @@ void Choice::BUILD() {
|
|||
choice_ctrl* temp;
|
||||
if (m_opt.gui_type != ConfigOptionDef::GUIType::undefined && m_opt.gui_type != ConfigOptionDef::GUIType::select_open) {
|
||||
m_is_editable = true;
|
||||
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
|
||||
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxTE_PROCESS_ENTER);
|
||||
}
|
||||
else {
|
||||
#ifdef __WXOSX__
|
||||
|
@ -988,46 +968,57 @@ void Choice::BUILD() {
|
|||
temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId());
|
||||
|
||||
if (m_is_editable) {
|
||||
temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) {
|
||||
temp->Bind(wxEVT_KILL_FOCUS, [this](wxEvent& e) {
|
||||
e.Skip();
|
||||
if (m_opt.type == coStrings) {
|
||||
on_change_field();
|
||||
return;
|
||||
}
|
||||
if (!bEnterPressed)
|
||||
propagate_value();
|
||||
} );
|
||||
|
||||
if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
|
||||
switch (m_opt.type) {
|
||||
case coFloatOrPercent:
|
||||
{
|
||||
std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : "";
|
||||
if (old_val == boost::any_cast<std::string>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case coInt:
|
||||
{
|
||||
int old_val = !m_value.empty() ? boost::any_cast<int>(m_value) : 0;
|
||||
if (old_val == boost::any_cast<int>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
|
||||
if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
|
||||
return;
|
||||
}
|
||||
}
|
||||
on_change_field();
|
||||
}
|
||||
else
|
||||
on_kill_focus();
|
||||
}), temp->GetId());
|
||||
temp->Bind(wxEVT_TEXT_ENTER, [this, temp](wxEvent& e) {
|
||||
EnterPressed enter(this);
|
||||
propagate_value();
|
||||
} );
|
||||
}
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
|
||||
}
|
||||
|
||||
void Choice::propagate_value()
|
||||
{
|
||||
if (m_opt.type == coStrings) {
|
||||
on_change_field();
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
|
||||
switch (m_opt.type) {
|
||||
case coFloatOrPercent:
|
||||
{
|
||||
std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : "";
|
||||
if (old_val == boost::any_cast<std::string>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case coInt:
|
||||
{
|
||||
int old_val = !m_value.empty() ? boost::any_cast<int>(m_value) : 0;
|
||||
if (old_val == boost::any_cast<int>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
|
||||
if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
|
||||
return;
|
||||
}
|
||||
}
|
||||
on_change_field();
|
||||
}
|
||||
else
|
||||
on_kill_focus();
|
||||
}
|
||||
|
||||
void Choice::suppress_scroll()
|
||||
{
|
||||
m_suppress_scroll = true;
|
||||
|
@ -1137,6 +1128,15 @@ void Choice::set_value(const boost::any& value, bool change_event)
|
|||
}
|
||||
else
|
||||
field->SetSelection(idx);
|
||||
|
||||
if (!m_value.empty() && m_opt.opt_key == "fill_density") {
|
||||
// If m_value was changed before, then update m_value here too to avoid case
|
||||
// when control's value is already changed from the ConfigManipulation::update_print_fff_config(),
|
||||
// but m_value doesn't respect it.
|
||||
if (double val; text_value.ToDouble(&val))
|
||||
m_value = val;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case coEnum: {
|
||||
|
|
|
@ -52,10 +52,17 @@ protected:
|
|||
//! in another case we can't unfocused control at all
|
||||
void on_kill_focus();
|
||||
/// Call the attached on_change method.
|
||||
void on_set_focus(wxEvent& event);
|
||||
/// Call the attached on_change method.
|
||||
void on_change_field();
|
||||
|
||||
class EnterPressed {
|
||||
public:
|
||||
EnterPressed(Field* field) :
|
||||
m_parent(field){ m_parent->set_enter_pressed(true); }
|
||||
~EnterPressed() { m_parent->set_enter_pressed(false); }
|
||||
private:
|
||||
Field* m_parent;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Call the attached m_back_to_initial_value method.
|
||||
void on_back_to_initial_value();
|
||||
|
@ -69,9 +76,6 @@ public:
|
|||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_kill_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_set_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_change m_on_change {nullptr};
|
||||
|
||||
|
@ -236,10 +240,6 @@ class TextCtrl : public Field {
|
|||
void change_field_value(wxEvent& event);
|
||||
#endif //__WXGTK__
|
||||
|
||||
#ifdef __WXOSX__
|
||||
bool bKilledFocus = false;
|
||||
#endif // __WXOSX__
|
||||
|
||||
public:
|
||||
TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
|
@ -342,6 +342,7 @@ public:
|
|||
|
||||
class Choice : public Field {
|
||||
using Field::Field;
|
||||
|
||||
public:
|
||||
Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
|
@ -349,6 +350,8 @@ public:
|
|||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value();
|
||||
|
||||
/* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value,
|
||||
* so let use a flag, which has TRUE value for a control without wxCB_READONLY style
|
||||
|
|
|
@ -65,6 +65,8 @@ enum {
|
|||
USB_PID_MMU_APP = 4,
|
||||
USB_PID_CW1_BOOT = 7,
|
||||
USB_PID_CW1_APP = 8,
|
||||
USB_PID_CW1S_BOOT = 14,
|
||||
USB_PID_CW1S_APP = 15,
|
||||
};
|
||||
|
||||
// This enum discriminates the kind of information in EVT_AVRDUDE,
|
||||
|
@ -308,7 +310,7 @@ void FirmwareDialog::priv::update_flash_enabled()
|
|||
void FirmwareDialog::priv::load_hex_file(const wxString &path)
|
||||
{
|
||||
hex_file = HexFile(path.wx_str());
|
||||
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
|
||||
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1 || hex_file.device == HexFile::DEV_CW1S;
|
||||
set_autodetect(autodetect);
|
||||
}
|
||||
|
||||
|
@ -636,6 +638,10 @@ void FirmwareDialog::priv::perform_upload()
|
|||
this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP));
|
||||
break;
|
||||
|
||||
case HexFile::DEV_CW1S:
|
||||
this->prepare_avr109(Avr109Pid(USB_PID_CW1S_BOOT, USB_PID_CW1S_APP));
|
||||
break;
|
||||
|
||||
default:
|
||||
this->prepare_mk2();
|
||||
break;
|
||||
|
@ -767,11 +773,10 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
|
|||
switch (usb_pid.boot) {
|
||||
case USB_PID_MMU_BOOT:
|
||||
return "Original Prusa MMU 2.0 Control";
|
||||
break;
|
||||
case USB_PID_CW1_BOOT:
|
||||
return "Original Prusa CW1";
|
||||
break;
|
||||
|
||||
case USB_PID_CW1S_BOOT:
|
||||
return "Original Prusa CW1S";
|
||||
default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2239,6 +2239,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
|||
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
|
||||
m_dirty |= mouse3d_controller_applied;
|
||||
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
|
||||
auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current();
|
||||
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
|
||||
|
||||
if (!m_dirty)
|
||||
return;
|
||||
|
@ -2780,11 +2782,10 @@ void GLCanvas3D::on_timer(wxTimerEvent& evt)
|
|||
|
||||
void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
|
||||
{
|
||||
// no need to do anything here
|
||||
// right after this event is recieved, idle event is fired
|
||||
|
||||
//m_dirty = true;
|
||||
//wxWakeUpIdle();
|
||||
// no need to wake up idle
|
||||
// right after this event, idle event is fired
|
||||
// m_dirty = true;
|
||||
// wxWakeUpIdle();
|
||||
}
|
||||
|
||||
|
||||
|
@ -2802,21 +2803,15 @@ void GLCanvas3D::schedule_extra_frame(int miliseconds)
|
|||
return;
|
||||
}
|
||||
}
|
||||
// Start timer
|
||||
int64_t now = timestamp_now();
|
||||
int remaining_time = m_render_timer.GetInterval();
|
||||
// Timer is not running
|
||||
if (! m_render_timer.IsRunning()) {
|
||||
m_extra_frame_requested_delayed = miliseconds;
|
||||
if (!m_render_timer.IsRunning()) {
|
||||
m_render_timer.StartOnce(miliseconds);
|
||||
m_render_timer_start = now;
|
||||
// Timer is running - restart only if new period is shorter than remaning period
|
||||
} else {
|
||||
const int64_t remaining_time = (m_render_timer_start + m_extra_frame_requested_delayed) - now;
|
||||
if (miliseconds + 20 < remaining_time) {
|
||||
m_render_timer.Stop();
|
||||
m_extra_frame_requested_delayed = miliseconds;
|
||||
m_render_timer.StartOnce(miliseconds);
|
||||
m_render_timer_start = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3455,12 +3450,17 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
|||
|
||||
// stores current min_z of instances
|
||||
std::map<std::pair<int, int>, double> min_zs;
|
||||
if (!snapshot_type.empty()) {
|
||||
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
|
||||
const ModelObject* obj = m_model->objects[i];
|
||||
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
|
||||
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
|
||||
const ModelObject* obj = m_model->objects[i];
|
||||
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
|
||||
if (snapshot_type.empty() && m_selection.get_object_idx() == i) {
|
||||
// This means we are flattening this object. In that case pretend
|
||||
// that it is not sinking (even if it is), so it is placed on bed
|
||||
// later on (whatever is sinking will be left sinking).
|
||||
min_zs[{ i, j }] = SINKING_Z_THRESHOLD;
|
||||
} else
|
||||
min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3502,7 +3502,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
|||
ModelObject* m = m_model->objects[i.first];
|
||||
const double shift_z = m->get_instance_min_z(i.second);
|
||||
// leave sinking instances as sinking
|
||||
if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) {
|
||||
if (min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) {
|
||||
const Vec3d shift(0.0, 0.0, -shift_z);
|
||||
m_selection.translate(i.first, i.second, shift);
|
||||
m->translate_instance(i.second, shift);
|
||||
|
@ -4433,13 +4433,7 @@ bool GLCanvas3D::_init_main_toolbar()
|
|||
}
|
||||
// init arrow
|
||||
BackgroundTexture::Metadata arrow_data;
|
||||
arrow_data.filename = "toolbar_arrow.png";
|
||||
// arrow_data.filename = "toolbar_arrow.svg";
|
||||
//arrow_data.left = 16;
|
||||
//arrow_data.top = 16;
|
||||
//arrow_data.right = 16;
|
||||
//arrow_data.bottom = 16;
|
||||
|
||||
arrow_data.filename = "toolbar_arrow.svg";
|
||||
arrow_data.left = 0;
|
||||
arrow_data.top = 0;
|
||||
arrow_data.right = 0;
|
||||
|
@ -5107,6 +5101,7 @@ void GLCanvas3D::_render_objects()
|
|||
m_volumes.set_z_range(-FLT_MAX, FLT_MAX);
|
||||
|
||||
m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data());
|
||||
m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances());
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
|
||||
if (shader != nullptr) {
|
||||
|
@ -6374,7 +6369,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
|
|||
case EWarning::ObjectOutside: text = _u8L("An object outside the print area was detected."); break;
|
||||
case EWarning::ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = ErrorType::SLICING_ERROR; break;
|
||||
case EWarning::SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break;
|
||||
case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible."); break;
|
||||
case EWarning::SomethingNotShown: text = _u8L("Some objects are not visible during editing."); break;
|
||||
case EWarning::ObjectClashed:
|
||||
text = _u8L("An object outside the print area was detected.\n"
|
||||
"Resolve the current problem to continue slicing.");
|
||||
|
|
|
@ -463,15 +463,13 @@ private:
|
|||
std::string m_sidebar_field;
|
||||
// when true renders an extra frame by not resetting m_dirty to false
|
||||
// see request_extra_frame()
|
||||
bool m_extra_frame_requested;
|
||||
int m_extra_frame_requested_delayed { std::numeric_limits<int>::max() };
|
||||
bool m_extra_frame_requested;
|
||||
bool m_event_handlers_bound{ false };
|
||||
|
||||
GLVolumeCollection m_volumes;
|
||||
GCodeViewer m_gcode_viewer;
|
||||
|
||||
RenderTimer m_render_timer;
|
||||
int64_t m_render_timer_start;
|
||||
|
||||
Selection m_selection;
|
||||
const DynamicPrintConfig* m_config;
|
||||
|
|
|
@ -169,6 +169,8 @@ void GLModel::reset()
|
|||
|
||||
void GLModel::render() const
|
||||
{
|
||||
GLShaderProgram* shader = wxGetApp().get_current_shader();
|
||||
|
||||
for (const RenderData& data : m_render_data) {
|
||||
if (data.vbo_id == 0 || data.ibo_id == 0)
|
||||
continue;
|
||||
|
@ -190,7 +192,6 @@ void GLModel::render() const
|
|||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_current_shader();
|
||||
if (shader != nullptr)
|
||||
shader->set_uniform("uniform_color", data.color);
|
||||
else
|
||||
|
|
|
@ -69,6 +69,8 @@ std::pair<bool, std::string> GLShadersManager::init()
|
|||
);
|
||||
// used to render variable layers heights in 3d editor
|
||||
valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" });
|
||||
// used to render highlight contour around selected triangles inside the multi-material gizmo
|
||||
valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" });
|
||||
|
||||
return { valid, error };
|
||||
}
|
||||
|
|
|
@ -193,10 +193,9 @@ bool GLToolbar::init_arrow(const BackgroundTexture::Metadata& arrow_texture)
|
|||
std::string path = resources_dir() + "/icons/";
|
||||
bool res = false;
|
||||
|
||||
if (!arrow_texture.filename.empty())
|
||||
res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false);
|
||||
// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100);
|
||||
|
||||
if (!arrow_texture.filename.empty()) {
|
||||
res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000);
|
||||
}
|
||||
if (res)
|
||||
m_arrow_texture.metadata = arrow_texture;
|
||||
|
||||
|
@ -1176,26 +1175,29 @@ void GLToolbar::render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighte
|
|||
float right = left + scaled_icons_size;
|
||||
|
||||
unsigned int tex_id = m_arrow_texture.texture.get_id();
|
||||
// width and height of icon arrow is pointing to
|
||||
float tex_width = (float)m_icons_texture.get_width();
|
||||
float tex_height = (float)m_icons_texture.get_height();
|
||||
// arrow width and height
|
||||
float arr_tex_width = (float)m_arrow_texture.texture.get_width();
|
||||
float arr_tex_height = (float)m_arrow_texture.texture.get_height();
|
||||
if ((tex_id != 0) && (arr_tex_width > 0) && (arr_tex_height > 0)) {
|
||||
float inv_tex_width = (arr_tex_width != 0.0f) ? 1.0f / arr_tex_width : 0.0f;
|
||||
float inv_tex_height = (arr_tex_height != 0.0f) ? 1.0f / arr_tex_height : 0.0f;
|
||||
|
||||
if ((tex_id != 0) && (tex_width > 0) && (tex_height > 0)) {
|
||||
float inv_tex_width = (tex_width != 0.0f) ? 1.0f / tex_width : 0.0f;
|
||||
float inv_tex_height = (tex_height != 0.0f) ? 1.0f / tex_height : 0.0f;
|
||||
|
||||
float internal_left = left + border - scaled_icons_size / 2; // add half scaled_icons_size for huge arrow
|
||||
float internal_right = right - border + scaled_icons_size / 2;
|
||||
float internal_left = left + border - scaled_icons_size * 1.5f; // add scaled_icons_size for huge arrow
|
||||
float internal_right = right - border + scaled_icons_size * 1.5f;
|
||||
float internal_top = top - border;
|
||||
// bottom is not moving and should be calculated from arrow texture sides ratio
|
||||
float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width();
|
||||
float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio;
|
||||
float internal_bottom = internal_top - (internal_right - internal_left) * arrow_sides_ratio ;
|
||||
|
||||
float internal_left_uv = (float)m_arrow_texture.metadata.left * inv_tex_width;
|
||||
float internal_right_uv = 1.0f - (float)m_arrow_texture.metadata.right * inv_tex_width;
|
||||
float internal_top_uv = 1.0f - (float)m_arrow_texture.metadata.top * inv_tex_height;
|
||||
float internal_bottom_uv = (float)m_arrow_texture.metadata.bottom * inv_tex_height;
|
||||
|
||||
GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv }, { internal_left_uv, internal_top_uv } });
|
||||
GLTexture::render_sub_texture(tex_id, internal_left, internal_right, internal_bottom, internal_top, { { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv }, { internal_left_uv, internal_bottom_uv } });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
|
@ -21,6 +22,7 @@
|
|||
|
||||
#include "AboutDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
|
@ -244,6 +246,127 @@ void warning_catcher(wxWindow* parent, const wxString& message)
|
|||
msg.ShowModal();
|
||||
}
|
||||
|
||||
static wxString bold(const wxString& str)
|
||||
{
|
||||
return wxString::Format("<b>%s</b>", str);
|
||||
};
|
||||
|
||||
static wxString bold_string(const wxString& str)
|
||||
{
|
||||
return wxString::Format("<b>\"%s\"</b>", str);
|
||||
};
|
||||
|
||||
static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes)
|
||||
{
|
||||
changes += "<table>";
|
||||
for (const ConfigSubstitution& conf_substitution : conf_substitutions) {
|
||||
wxString new_val;
|
||||
const ConfigOptionDef* def = conf_substitution.opt_def;
|
||||
if (!def)
|
||||
continue;
|
||||
switch (def->type) {
|
||||
case coEnum:
|
||||
{
|
||||
const std::vector<std::string>& labels = def->enum_labels;
|
||||
const std::vector<std::string>& values = def->enum_values;
|
||||
int val = conf_substitution.new_value->getInt();
|
||||
|
||||
bool is_infill = def->opt_key == "top_fill_pattern" ||
|
||||
def->opt_key == "bottom_fill_pattern" ||
|
||||
def->opt_key == "fill_pattern";
|
||||
|
||||
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
|
||||
// So we should "convert" val to the correct one
|
||||
if (is_infill) {
|
||||
for (const auto& key_val : *def->enum_keys_map)
|
||||
if ((int)key_val.second == val) {
|
||||
auto it = std::find(values.begin(), values.end(), key_val.first);
|
||||
if (it == values.end())
|
||||
break;
|
||||
auto idx = it - values.begin();
|
||||
new_val = wxString("\"") + values[idx] + "\"" + " (" + from_u8(_utf8(labels[idx])) + ")";
|
||||
break;
|
||||
}
|
||||
if (new_val.IsEmpty()) {
|
||||
assert(false);
|
||||
new_val = _L("Undefined");
|
||||
}
|
||||
}
|
||||
else
|
||||
new_val = wxString("\"") + values[val] + "\"" + " (" + from_u8(_utf8(labels[val])) + ")";
|
||||
break;
|
||||
}
|
||||
case coBool:
|
||||
new_val = conf_substitution.new_value->getBool() ? "true" : "false";
|
||||
break;
|
||||
case coBools:
|
||||
if (conf_substitution.new_value->nullable())
|
||||
for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values)
|
||||
new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", ";
|
||||
else
|
||||
for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values)
|
||||
new_val += std::string(v ? "true" : "false") + ", ";
|
||||
if (! new_val.empty())
|
||||
new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end());
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) +
|
||||
format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) +
|
||||
"</td></tr>";
|
||||
}
|
||||
changes += "</table>";
|
||||
}
|
||||
|
||||
static wxString substitution_message(const wxString& changes)
|
||||
{
|
||||
return
|
||||
_L("Most likely the configuration was produced by a newer version of PrusaSlicer or by some PrusaSlicer fork.") + " " +
|
||||
_L("The following values were substituted:") + "\n" + changes + "\n\n" +
|
||||
_L("Review the substitutions and adjust them if needed.");
|
||||
}
|
||||
|
||||
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions)
|
||||
{
|
||||
wxString changes;
|
||||
|
||||
auto preset_type_name = [](Preset::Type type) {
|
||||
switch (type) {
|
||||
case Preset::TYPE_PRINT: return _L("Print settings");
|
||||
case Preset::TYPE_SLA_PRINT: return _L("SLA print settings");
|
||||
case Preset::TYPE_FILAMENT: return _L("Filament");
|
||||
case Preset::TYPE_SLA_MATERIAL: return _L("SLA material");
|
||||
case Preset::TYPE_PRINTER: return _L("Printer");
|
||||
case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer");
|
||||
default: assert(false); return wxString();
|
||||
}
|
||||
};
|
||||
|
||||
for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) {
|
||||
changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name));
|
||||
if (!substitution.preset_file.empty())
|
||||
changes += format_wxstr(" (%1%)", substitution.preset_file);
|
||||
|
||||
add_config_substitutions(substitution.substitutions, changes);
|
||||
}
|
||||
|
||||
InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes));
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename)
|
||||
{
|
||||
wxString changes = "\n";
|
||||
add_config_substitutions(config_substitutions, changes);
|
||||
|
||||
InfoDialog msg(nullptr,
|
||||
format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)),
|
||||
substitution_message(changes));
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
|
||||
{
|
||||
if (comboCtrl == nullptr)
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace boost::filesystem { class path; }
|
|||
#include <wx/string.h>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
|
||||
class wxWindow;
|
||||
class wxMenuBar;
|
||||
|
@ -48,6 +49,8 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title
|
|||
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
|
||||
inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); }
|
||||
void warning_catcher(wxWindow* parent, const wxString& message);
|
||||
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions);
|
||||
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename);
|
||||
|
||||
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
|
||||
// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).
|
||||
|
|
|
@ -87,6 +87,11 @@
|
|||
#include <boost/nowide/fstream.hpp>
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
|
||||
// Needed for forcing menu icons back under gtk2 and gtk3
|
||||
#if defined(__WXGTK20__) || defined(__WXGTK3__)
|
||||
#include <gtk/gtk.h>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
@ -435,7 +440,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
|
|||
|
||||
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
|
||||
|
||||
/* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1",
|
||||
/* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S",
|
||||
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
|
||||
/* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1",
|
||||
};
|
||||
|
||||
std::string out = defaults[file_type];
|
||||
|
@ -626,12 +633,8 @@ void GUI_App::post_init()
|
|||
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
|
||||
}
|
||||
else {
|
||||
if (! this->init_params->preset_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (! this->init_params->preset_substitutions.empty())
|
||||
show_substitutions_info(this->init_params->preset_substitutions);
|
||||
|
||||
#if 0
|
||||
// Load the cummulative config over the currently active profiles.
|
||||
|
@ -664,7 +667,7 @@ void GUI_App::post_init()
|
|||
|
||||
// show "Did you know" notification
|
||||
if (app_config->get("show_hints") == "1" && ! is_gcode_viewer())
|
||||
plater_->get_notification_manager()->push_hint_notification();
|
||||
plater_->get_notification_manager()->push_hint_notification(true);
|
||||
|
||||
// The extra CallAfter() is needed because of Mac, where this is the only way
|
||||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
|
@ -801,6 +804,14 @@ bool GUI_App::OnInit()
|
|||
|
||||
bool GUI_App::on_init_inner()
|
||||
{
|
||||
// Forcing back menu icons under gtk2 and gtk3. Solution is based on:
|
||||
// https://docs.gtk.org/gtk3/class.Settings.html
|
||||
// see also https://docs.wxwidgets.org/3.0/classwx_menu_item.html#a2b5d6bcb820b992b1e4709facbf6d4fb
|
||||
// TODO: Find workaround for GTK4
|
||||
#if defined(__WXGTK20__) || defined(__WXGTK3__)
|
||||
g_object_set (gtk_settings_get_default (), "gtk-menu-images", TRUE, NULL);
|
||||
#endif
|
||||
|
||||
// Verify resources path
|
||||
const wxString resources_dir = from_u8(Slic3r::resources_dir());
|
||||
wxCHECK_MSG(wxDirExists(resources_dir), false,
|
||||
|
@ -916,7 +927,10 @@ bool GUI_App::on_init_inner()
|
|||
// Suppress the '- default -' presets.
|
||||
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
|
||||
try {
|
||||
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
// Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
|
||||
// If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
|
||||
// installation of a compatible system preset, thus nullifying the system preset substitutions.
|
||||
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(nullptr, ex.what());
|
||||
}
|
||||
|
@ -1029,17 +1043,22 @@ bool GUI_App::dark_mode()
|
|||
#endif
|
||||
}
|
||||
|
||||
const wxColour GUI_App::get_label_default_clr_system()
|
||||
{
|
||||
return dark_mode() ? wxColour(115, 220, 103) : wxColour(26, 132, 57);
|
||||
}
|
||||
|
||||
const wxColour GUI_App::get_label_default_clr_modified()
|
||||
{
|
||||
return dark_mode() ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
|
||||
}
|
||||
|
||||
void GUI_App::init_label_colours()
|
||||
{
|
||||
m_color_label_modified = get_label_default_clr_modified();
|
||||
m_color_label_sys = get_label_default_clr_system();
|
||||
|
||||
bool is_dark_mode = dark_mode();
|
||||
if (is_dark_mode) {
|
||||
m_color_label_modified = wxColour(253, 111, 40);
|
||||
m_color_label_sys = wxColour(115, 220, 103);
|
||||
}
|
||||
else {
|
||||
m_color_label_modified = wxColour(252, 77, 1);
|
||||
m_color_label_sys = wxColour(26, 132, 57);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
|
||||
|
@ -1800,10 +1819,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots"));
|
||||
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
|
||||
local_menu->Append(config_id_base + ConfigMenuUpdate, _L("Check for updates"), _L("Check for configuration updates"));
|
||||
#ifdef __linux__
|
||||
if (DesktopIntegrationDialog::integration_possible())
|
||||
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
|
||||
#endif
|
||||
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
//if (DesktopIntegrationDialog::integration_possible())
|
||||
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
|
||||
#endif //(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
|
||||
local_menu->AppendSeparator();
|
||||
}
|
||||
local_menu->Append(config_id_base + ConfigMenuPreferences, _L("&Preferences") + dots +
|
||||
|
@ -1861,9 +1880,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
child->SetFont(normal_font());
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
app_config->set("on_snapshot",
|
||||
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
|
||||
*app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
|
||||
if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error(
|
||||
*app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data());
|
||||
snapshot != nullptr)
|
||||
app_config->set("on_snapshot", snapshot->id);
|
||||
}
|
||||
break;
|
||||
case ConfigMenuSnapshots:
|
||||
|
@ -1874,19 +1894,24 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
|
||||
dlg.ShowModal();
|
||||
if (!dlg.snapshot_to_activate().empty()) {
|
||||
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
|
||||
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
|
||||
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) &&
|
||||
! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "",
|
||||
GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate())))
|
||||
break;
|
||||
try {
|
||||
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
|
||||
// Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system
|
||||
// presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users
|
||||
// are known to be creative and mess with the config files in various ways.
|
||||
if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
! all_substitutions.empty()) {
|
||||
// TODO:
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
! all_substitutions.empty())
|
||||
show_substitutions_info(all_substitutions);
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
load_current_presets();
|
||||
|
||||
// update config wizard in respect to the new config
|
||||
update_wizard_from_config();
|
||||
} catch (std::exception &ex) {
|
||||
GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
|
||||
}
|
||||
|
@ -2059,7 +2084,7 @@ std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets(
|
|||
// to notify the user whether he is aware that some preset changes will be lost.
|
||||
bool GUI_App::check_and_save_current_preset_changes(const wxString& header)
|
||||
{
|
||||
if (this->plater()->model().objects.empty() && has_current_preset_changes()) {
|
||||
if (/*this->plater()->model().objects.empty() && */has_current_preset_changes()) {
|
||||
UnsavedChangesDialog dlg(header);
|
||||
if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
@ -2138,6 +2163,17 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
|
|||
}
|
||||
}
|
||||
|
||||
void GUI_App::update_wizard_from_config()
|
||||
{
|
||||
if (!m_wizard)
|
||||
return;
|
||||
// If ConfigWizard was created before changing of the configuration,
|
||||
// we have to destroy it to have possibility to create it again in respect to the new config's parameters
|
||||
m_wizard->Reparent(nullptr);
|
||||
m_wizard->Destroy();
|
||||
m_wizard = nullptr;
|
||||
}
|
||||
|
||||
bool GUI_App::OnExceptionInMainLoop()
|
||||
{
|
||||
generic_exception_handle();
|
||||
|
@ -2291,14 +2327,20 @@ wxString GUI_App::current_language_code_safe() const
|
|||
|
||||
void GUI_App::open_web_page_localized(const std::string &http_address)
|
||||
{
|
||||
wxLaunchDefaultBrowser(http_address + "&lng=" + this->current_language_code_safe());
|
||||
open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe());
|
||||
}
|
||||
|
||||
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
|
||||
{
|
||||
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
|
||||
|
||||
if (reason == ConfigWizard::RR_USER)
|
||||
if (PresetUpdater::UpdateResult result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD);
|
||||
result == PresetUpdater::R_ALL_CANCELED)
|
||||
return false;
|
||||
|
||||
if (! m_wizard) {
|
||||
wxBusyCursor wait;
|
||||
m_wizard = new ConfigWizard(mainframe);
|
||||
}
|
||||
|
||||
|
@ -2466,7 +2508,7 @@ void GUI_App::check_updates(const bool verbose)
|
|||
{
|
||||
PresetUpdater::UpdateResult updater_result;
|
||||
try {
|
||||
updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
|
||||
updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
|
||||
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
|
||||
mainframe->Close();
|
||||
}
|
||||
|
@ -2483,6 +2525,23 @@ void GUI_App::check_updates(const bool verbose)
|
|||
}
|
||||
}
|
||||
|
||||
bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/)
|
||||
{
|
||||
bool launch = true;
|
||||
|
||||
if (get_app_config()->get("suppress_hyperlinks").empty()) {
|
||||
wxRichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
|
||||
dialog.ShowCheckBox(_L("Remember my choice"));
|
||||
int answer = dialog.ShowModal();
|
||||
launch = answer == wxID_YES;
|
||||
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
|
||||
}
|
||||
if (launch)
|
||||
launch = get_app_config()->get("suppress_hyperlinks") != "1";
|
||||
|
||||
return launch && wxLaunchDefaultBrowser(url, flags);
|
||||
}
|
||||
|
||||
// static method accepting a wxWindow object as first parameter
|
||||
// void warning_catcher{
|
||||
// my($self, $message_dialog) = @_;
|
||||
|
|
|
@ -66,7 +66,9 @@ enum FileType
|
|||
|
||||
FT_TEX,
|
||||
|
||||
FT_PNGZIP,
|
||||
FT_SL1,
|
||||
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
|
||||
FT_SL1S,
|
||||
|
||||
FT_SIZE,
|
||||
};
|
||||
|
@ -177,6 +179,8 @@ public:
|
|||
|
||||
static unsigned get_colour_approx_luma(const wxColour &colour);
|
||||
static bool dark_mode();
|
||||
const wxColour get_label_default_clr_system();
|
||||
const wxColour get_label_default_clr_modified();
|
||||
void init_label_colours();
|
||||
void update_label_colours_from_appconfig();
|
||||
void update_label_colours();
|
||||
|
@ -247,6 +251,7 @@ public:
|
|||
bool check_print_host_queue();
|
||||
bool checked_tab(Tab* tab);
|
||||
void load_current_presets(bool check_printer_presets = true);
|
||||
void update_wizard_from_config();
|
||||
|
||||
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
|
||||
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
|
||||
|
@ -256,7 +261,8 @@ public:
|
|||
void open_preferences(size_t open_on_tab = 0);
|
||||
|
||||
virtual bool OnExceptionInMainLoop() override;
|
||||
|
||||
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
|
||||
bool open_browser_with_warning_dialog(const wxString& url, int flags = 0);
|
||||
#ifdef __APPLE__
|
||||
void OSXStoreOpenFiles(const wxArrayString &files) override;
|
||||
// wxWidgets override to get an event on open files.
|
||||
|
|
|
@ -657,6 +657,15 @@ wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu)
|
|||
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "",
|
||||
[](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu,
|
||||
[]() {return plater()->can_fix_through_netfabb(); }, plater());
|
||||
|
||||
return menu_item;
|
||||
}
|
||||
|
||||
wxMenuItem* MenuFactory::append_menu_item_simplify(wxMenu* menu)
|
||||
{
|
||||
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Simplify model"), "",
|
||||
[](wxCommandEvent&) { obj_list()->simplify(); }, "", menu,
|
||||
[]() {return plater()->can_simplify(); }, plater());
|
||||
menu->AppendSeparator();
|
||||
|
||||
return menu_item;
|
||||
|
@ -874,6 +883,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu)
|
|||
append_menu_item_scale_selection_to_fit_print_volume(menu);
|
||||
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
}
|
||||
|
||||
|
@ -923,6 +933,7 @@ void MenuFactory::create_part_menu()
|
|||
append_menu_item_replace_with_stl(menu);
|
||||
append_menu_item_export_stl(menu);
|
||||
append_menu_item_fix_through_netfabb(menu);
|
||||
append_menu_item_simplify(menu);
|
||||
append_menu_items_mirror(menu);
|
||||
|
||||
append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"),
|
||||
|
|
|
@ -92,6 +92,7 @@ private:
|
|||
wxMenuItem* append_menu_item_printable(wxMenu* menu);
|
||||
void append_menu_items_osx(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_simplify(wxMenu* menu);
|
||||
void append_menu_item_export_stl(wxMenu* menu);
|
||||
void append_menu_item_reload_from_disk(wxMenu* menu);
|
||||
void append_menu_item_replace_with_stl(wxMenu* menu);
|
||||
|
|
|
@ -145,7 +145,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus
|
|||
auto temp = new wxStaticText(m_parent, wxID_ANY, _L("mm"));
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
temp->SetFont(wxGetApp().normal_font());
|
||||
sizer->Add(temp, 0, wxLEFT, wxGetApp().em_unit());
|
||||
sizer->Add(temp, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, wxGetApp().em_unit());
|
||||
|
||||
m_grid_sizer->Add(sizer);
|
||||
|
||||
|
|
|
@ -1049,7 +1049,7 @@ void ObjectList::key_event(wxKeyEvent& event)
|
|||
|| event.GetKeyCode() == WXK_BACK
|
||||
#endif //__WXOSX__
|
||||
) {
|
||||
remove();
|
||||
wxGetApp().plater()->remove_selected();
|
||||
}
|
||||
else if (event.GetKeyCode() == WXK_F5)
|
||||
wxGetApp().plater()->reload_all_from_disk();
|
||||
|
@ -1778,10 +1778,8 @@ void ObjectList::del_subobject_item(wxDataViewItem& item)
|
|||
del_layers_from_object(obj_idx);
|
||||
else if (type & itLayer && obj_idx != -1)
|
||||
del_layer_from_object(obj_idx, m_objects_model->GetLayerRangeByItem(item));
|
||||
else if (type & itInfo && obj_idx != -1) {
|
||||
Unselect(item);
|
||||
Select(parent);
|
||||
}
|
||||
else if (type & itInfo && obj_idx != -1)
|
||||
del_info_item(obj_idx, m_objects_model->GetInfoItemType(item));
|
||||
else if (idx == -1)
|
||||
return;
|
||||
else if (!del_subobject_from_object(obj_idx, idx, type))
|
||||
|
@ -1795,6 +1793,52 @@ void ObjectList::del_subobject_item(wxDataViewItem& item)
|
|||
update_info_items(obj_idx);
|
||||
}
|
||||
|
||||
void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
|
||||
{
|
||||
Plater* plater = wxGetApp().plater();
|
||||
GLCanvas3D* cnv = plater->canvas3D();
|
||||
|
||||
switch (type) {
|
||||
case InfoItemType::CustomSupports:
|
||||
cnv->get_gizmos_manager().reset_all_states();
|
||||
Plater::TakeSnapshot(plater, _L("Remove paint-on supports"));
|
||||
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
||||
mv->supported_facets.clear();
|
||||
break;
|
||||
|
||||
case InfoItemType::CustomSeam:
|
||||
cnv->get_gizmos_manager().reset_all_states();
|
||||
Plater::TakeSnapshot(plater, _L("Remove paint-on seam"));
|
||||
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
||||
mv->seam_facets.clear();
|
||||
break;
|
||||
|
||||
case InfoItemType::MmuSegmentation:
|
||||
cnv->get_gizmos_manager().reset_all_states();
|
||||
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
|
||||
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
|
||||
mv->mmu_segmentation_facets.clear();
|
||||
break;
|
||||
|
||||
case InfoItemType::Sinking:
|
||||
Plater::TakeSnapshot(plater, _L("Shift objects to bed"));
|
||||
(*m_objects)[obj_idx]->ensure_on_bed();
|
||||
cnv->reload_scene(true, true);
|
||||
break;
|
||||
|
||||
case InfoItemType::VariableLayerHeight:
|
||||
Plater::TakeSnapshot(plater, _L("Remove variable layer height"));
|
||||
(*m_objects)[obj_idx]->layer_height_profile.clear();
|
||||
if (cnv->is_layers_editing_enabled())
|
||||
//cnv->post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
|
||||
cnv->force_main_toolbar_left_action(cnv->get_main_toolbar_item_id("layersediting"));
|
||||
break;
|
||||
|
||||
case InfoItemType::Undef : assert(false); break;
|
||||
}
|
||||
cnv->post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
|
||||
void ObjectList::del_settings_from_config(const wxDataViewItem& parent_item)
|
||||
{
|
||||
const bool is_layer_settings = m_objects_model->GetItemType(parent_item) == itLayer;
|
||||
|
@ -1876,16 +1920,15 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
|
|||
if (vol->is_model_part())
|
||||
++solid_cnt;
|
||||
if (volume->is_model_part() && solid_cnt == 1) {
|
||||
Slic3r::GUI::show_error(nullptr, _(L("From Object List You can't delete the last solid part from object.")));
|
||||
Slic3r::GUI::show_error(nullptr, _L("From Object List You can't delete the last solid part from object."));
|
||||
return false;
|
||||
}
|
||||
|
||||
take_snapshot(_(L("Delete Subobject")));
|
||||
take_snapshot(_L("Delete Subobject"));
|
||||
|
||||
object->delete_volume(idx);
|
||||
|
||||
if (object->volumes.size() == 1)
|
||||
{
|
||||
if (object->volumes.size() == 1) {
|
||||
const auto last_volume = object->volumes[0];
|
||||
if (!last_volume->config.empty()) {
|
||||
object->config.apply(last_volume->config);
|
||||
|
@ -1904,11 +1947,11 @@ bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, con
|
|||
}
|
||||
else if (type == itInstance) {
|
||||
if (object->instances.size() == 1) {
|
||||
Slic3r::GUI::show_error(nullptr, _(L("Last instance of an object cannot be deleted.")));
|
||||
Slic3r::GUI::show_error(nullptr, _L("Last instance of an object cannot be deleted."));
|
||||
return false;
|
||||
}
|
||||
|
||||
take_snapshot(_(L("Delete Instance")));
|
||||
take_snapshot(_L("Delete Instance"));
|
||||
object->delete_instance(idx);
|
||||
}
|
||||
else
|
||||
|
@ -2517,7 +2560,7 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D
|
|||
}
|
||||
|
||||
|
||||
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/)
|
||||
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/, bool added_object/* = false*/)
|
||||
{
|
||||
const ModelObject* model_object = (*m_objects)[obj_idx];
|
||||
wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx);
|
||||
|
@ -2561,8 +2604,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
|
|||
if (! shows && should_show) {
|
||||
m_objects_model->AddInfoChild(item_obj, type);
|
||||
Expand(item_obj);
|
||||
wxGetApp().notification_manager()->push_updated_item_info_notification(type);
|
||||
|
||||
if (added_object)
|
||||
wxGetApp().notification_manager()->push_updated_item_info_notification(type);
|
||||
}
|
||||
else if (shows && ! should_show) {
|
||||
if (!selections)
|
||||
|
@ -2594,7 +2637,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
|
|||
model_object->config.has("extruder") ? model_object->config.extruder() : 0,
|
||||
get_mesh_errors_count(obj_idx) > 0);
|
||||
|
||||
update_info_items(obj_idx);
|
||||
update_info_items(obj_idx, nullptr, true);
|
||||
|
||||
// add volumes to the object
|
||||
if (model_object->volumes.size() > 1) {
|
||||
|
@ -2687,8 +2730,9 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
|
|||
return;
|
||||
|
||||
m_prevent_list_events = true;
|
||||
for (std::vector<ItemForDelete>::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item)
|
||||
{
|
||||
|
||||
std::set<size_t> modified_objects_ids;
|
||||
for (std::vector<ItemForDelete>::const_reverse_iterator item = items_for_delete.rbegin(); item != items_for_delete.rend(); ++item) {
|
||||
if (!(item->type&(itObject | itVolume | itInstance)))
|
||||
continue;
|
||||
if (item->type&itObject) {
|
||||
|
@ -2698,8 +2742,7 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
|
|||
else {
|
||||
if (!del_subobject_from_object(item->obj_idx, item->sub_obj_idx, item->type))
|
||||
continue;
|
||||
if (item->type&itVolume)
|
||||
{
|
||||
if (item->type&itVolume) {
|
||||
m_objects_model->Delete(m_objects_model->GetItemByVolumeId(item->obj_idx, item->sub_obj_idx));
|
||||
ModelObject* obj = object(item->obj_idx);
|
||||
if (obj->volumes.size() == 1) {
|
||||
|
@ -2717,7 +2760,14 @@ void ObjectList::delete_from_model_and_list(const std::vector<ItemForDelete>& it
|
|||
else
|
||||
m_objects_model->Delete(m_objects_model->GetItemByInstanceId(item->obj_idx, item->sub_obj_idx));
|
||||
}
|
||||
|
||||
modified_objects_ids.insert(static_cast<size_t>(item->obj_idx));
|
||||
}
|
||||
|
||||
for (size_t id : modified_objects_ids) {
|
||||
update_info_items(id);
|
||||
}
|
||||
|
||||
m_prevent_list_events = true;
|
||||
part_selection_changed();
|
||||
}
|
||||
|
@ -3360,6 +3410,18 @@ void ObjectList::update_selections_on_canvas()
|
|||
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
|
||||
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
|
||||
}
|
||||
else if (type == itInfo) {
|
||||
// When selecting an info item, select one instance of the
|
||||
// respective object - a gizmo may want to be opened.
|
||||
int inst_idx = selection.get_instance_idx();
|
||||
int scene_obj_idx = selection.get_object_idx();
|
||||
mode = Selection::Instance;
|
||||
// select first instance, unless an instance of the object is already selected
|
||||
if (scene_obj_idx == -1 || inst_idx == -1 || scene_obj_idx != obj_idx)
|
||||
inst_idx = 0;
|
||||
std::vector<unsigned int> idxs = selection.get_volume_idxs_from_instance(obj_idx, inst_idx);
|
||||
volume_idxs.insert(volume_idxs.end(), idxs.begin(), idxs.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
mode = Selection::Instance;
|
||||
|
@ -3374,7 +3436,7 @@ void ObjectList::update_selections_on_canvas()
|
|||
|
||||
if (sel_cnt == 1) {
|
||||
wxDataViewItem item = GetSelection();
|
||||
if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer | itInfo))
|
||||
if (m_objects_model->GetItemType(item) & (itSettings | itInstanceRoot | itLayerRoot | itLayer))
|
||||
add_to_selection(m_objects_model->GetParent(item), selection, instance_idx, mode);
|
||||
else
|
||||
add_to_selection(item, selection, instance_idx, mode);
|
||||
|
@ -3857,6 +3919,7 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set
|
|||
|
||||
// update printable state for new volumes on canvas3D
|
||||
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(new_obj_indx);
|
||||
update_info_items(new_obj_indx);
|
||||
}
|
||||
|
||||
void ObjectList::instances_to_separated_objects(const int obj_idx)
|
||||
|
@ -3889,6 +3952,8 @@ void ObjectList::instances_to_separated_objects(const int obj_idx)
|
|||
|
||||
// update printable state for new volumes on canvas3D
|
||||
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs);
|
||||
for (size_t object : object_idxs)
|
||||
update_info_items(object);
|
||||
}
|
||||
|
||||
void ObjectList::split_instances()
|
||||
|
@ -3953,6 +4018,29 @@ void ObjectList::fix_through_netfabb()
|
|||
wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx);
|
||||
|
||||
update_item_error_icon(obj_idx, vol_idx);
|
||||
update_info_items(obj_idx);
|
||||
}
|
||||
|
||||
void ObjectList::simplify()
|
||||
{
|
||||
auto plater = wxGetApp().plater();
|
||||
GLGizmosManager& gizmos_mgr = plater->canvas3D()->get_gizmos_manager();
|
||||
|
||||
// Do not simplify when a gizmo is open. There might be issues with updates
|
||||
// and what is worse, the snapshot time would refer to the internal stack.
|
||||
auto current_type = gizmos_mgr.get_current_type();
|
||||
if (current_type == GLGizmosManager::Simplify) {
|
||||
// close first
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
}else if (current_type != GLGizmosManager::Undefined) {
|
||||
plater->get_notification_manager()->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("ERROR: Please close all manipulators available from "
|
||||
"the left toolbar before start simplify the mesh."));
|
||||
return;
|
||||
}
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
}
|
||||
|
||||
void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const
|
||||
|
@ -3972,6 +4060,8 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co
|
|||
// unmark fixed item only
|
||||
m_objects_model->DeleteWarningIcon(item);
|
||||
}
|
||||
else
|
||||
m_objects_model->AddWarningIcon(item);
|
||||
}
|
||||
|
||||
void ObjectList::msw_rescale()
|
||||
|
@ -4240,5 +4330,10 @@ ModelObject* ObjectList::object(const int obj_idx) const
|
|||
return (*m_objects)[obj_idx];
|
||||
}
|
||||
|
||||
bool ObjectList::has_paint_on_segmentation()
|
||||
{
|
||||
return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
|
||||
}
|
||||
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
|
|
|
@ -254,6 +254,7 @@ public:
|
|||
void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range);
|
||||
void del_layers_from_object(const int obj_idx);
|
||||
bool del_subobject_from_object(const int obj_idx, const int idx, const int type);
|
||||
void del_info_item(const int obj_idx, InfoItemType type);
|
||||
void split();
|
||||
void merge(bool to_multipart_object);
|
||||
void layers_editing();
|
||||
|
@ -350,13 +351,14 @@ public:
|
|||
void update_and_show_object_settings_item();
|
||||
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
|
||||
void update_object_list_by_printer_technology();
|
||||
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr);
|
||||
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr, bool added_object = false);
|
||||
|
||||
void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx);
|
||||
void instances_to_separated_objects(const int obj_idx);
|
||||
void split_instances();
|
||||
void rename_item();
|
||||
void fix_through_netfabb();
|
||||
void simplify();
|
||||
void update_item_error_icon(const int obj_idx, int vol_idx) const ;
|
||||
|
||||
void copy_layers_to_clipboard();
|
||||
|
@ -378,6 +380,7 @@ public:
|
|||
void set_extruder_for_selected_items(const int extruder) const ;
|
||||
wxDataViewItemArray reorder_volumes_and_get_selection(int obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
|
||||
void apply_volumes_order();
|
||||
bool has_paint_on_segmentation();
|
||||
|
||||
private:
|
||||
#ifdef __WXOSX__
|
||||
|
|
|
@ -709,9 +709,9 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
|
|||
const ExPolygons& bottom = object->get_layer(0)->lslices;
|
||||
double bottom_area = area(bottom);
|
||||
|
||||
// at least 30% of object's height have to be a solid
|
||||
int i;
|
||||
for (i = 1; i < int(0.3 * num_layers); ++ i) {
|
||||
// at least 25% of object's height have to be a solid
|
||||
int i, min_solid_height = int(0.25 * num_layers);
|
||||
for (i = 1; i <= min_solid_height; ++ i) {
|
||||
double cur_area = area(object->get_layer(i)->lslices);
|
||||
if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) {
|
||||
// but due to the elephant foot compensation, the first layer may be slightly smaller than the others
|
||||
|
@ -723,7 +723,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (i < int(0.3 * num_layers))
|
||||
if (i < min_solid_height)
|
||||
continue;
|
||||
|
||||
// bottom layer have to be a biggest, so control relation between bottom layer and object size
|
||||
|
@ -788,11 +788,11 @@ void Preview::update_layers_slider_mode()
|
|||
object->config.option("extruder")->getInt() != extruder)
|
||||
return false;
|
||||
|
||||
if (object->volumes.size() > 1)
|
||||
for (ModelVolume* volume : object->volumes)
|
||||
if (volume->config.has("extruder") &&
|
||||
volume->config.option("extruder")->getInt() != extruder)
|
||||
return false;
|
||||
for (ModelVolume* volume : object->volumes)
|
||||
if ((volume->config.has("extruder") &&
|
||||
volume->config.option("extruder")->getInt() != extruder) ||
|
||||
!volume->mmu_segmentation_facets.empty())
|
||||
return false;
|
||||
|
||||
for (const auto& range : object->layer_config_ranges)
|
||||
if (range.second.has("extruder") &&
|
||||
|
|
|
@ -85,6 +85,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u
|
|||
, m_dragging(false)
|
||||
, m_imgui(wxGetApp().imgui())
|
||||
, m_first_input_window_render(true)
|
||||
, m_dirty(false)
|
||||
{
|
||||
m_base_color = DEFAULT_BASE_COLOR;
|
||||
m_drag_color = DEFAULT_DRAG_COLOR;
|
||||
|
@ -154,6 +155,13 @@ void GLGizmoBase::update(const UpdateData& data)
|
|||
on_update(data);
|
||||
}
|
||||
|
||||
bool GLGizmoBase::update_items_state()
|
||||
{
|
||||
bool res = m_dirty;
|
||||
m_dirty = false;
|
||||
return res;
|
||||
};
|
||||
|
||||
std::array<float, 4> GLGizmoBase::picking_color_component(unsigned int id) const
|
||||
{
|
||||
static const float INV_255 = 1.0f / 255.0f;
|
||||
|
@ -209,6 +217,10 @@ std::string GLGizmoBase::format(float value, unsigned int decimals) const
|
|||
return Slic3r::string_printf("%.*f", decimals, value);
|
||||
}
|
||||
|
||||
void GLGizmoBase::set_dirty() {
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
on_render_input_window(x, y, bottom_limit);
|
||||
|
|
|
@ -154,6 +154,9 @@ public:
|
|||
|
||||
void update(const UpdateData& data);
|
||||
|
||||
// returns True when Gizmo changed its state
|
||||
bool update_items_state();
|
||||
|
||||
void render() { m_tooltip.clear(); on_render(); }
|
||||
void render_for_picking() { on_render_for_picking(); }
|
||||
void render_input_window(float x, float y, float bottom_limit);
|
||||
|
@ -187,6 +190,13 @@ protected:
|
|||
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
|
||||
|
||||
std::string format(float value, unsigned int decimals) const;
|
||||
|
||||
// Mark gizmo as dirty to Re-Render when idle()
|
||||
void set_dirty();
|
||||
private:
|
||||
// Flag for dirty visible state of Gizmo
|
||||
// When True then need new rendering
|
||||
bool m_dirty;
|
||||
};
|
||||
|
||||
// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components
|
||||
|
|
|
@ -282,21 +282,21 @@ void GLGizmoCut::update_contours()
|
|||
const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
const BoundingBoxf3& box = first_glvolume->transformed_convex_hull_bounding_box();
|
||||
|
||||
const int object_idx = selection.get_object_idx();
|
||||
const ModelObject* model_object = wxGetApp().model().objects[selection.get_object_idx()];
|
||||
const int instance_idx = selection.get_instance_idx();
|
||||
|
||||
if (0.0 < m_cut_z && m_cut_z < m_max_z) {
|
||||
if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_idx != object_idx || m_cut_contours.instance_idx != instance_idx) {
|
||||
if (m_cut_contours.cut_z != m_cut_z || m_cut_contours.object_id != model_object->id() || m_cut_contours.instance_idx != instance_idx) {
|
||||
m_cut_contours.cut_z = m_cut_z;
|
||||
|
||||
if (m_cut_contours.object_idx != object_idx) {
|
||||
m_cut_contours.mesh = wxGetApp().plater()->model().objects[object_idx]->raw_mesh();
|
||||
if (m_cut_contours.object_id != model_object->id()) {
|
||||
m_cut_contours.mesh = model_object->raw_mesh();
|
||||
m_cut_contours.mesh.repair();
|
||||
}
|
||||
|
||||
m_cut_contours.position = box.center();
|
||||
m_cut_contours.shift = Vec3d::Zero();
|
||||
m_cut_contours.object_idx = object_idx;
|
||||
m_cut_contours.object_id = model_object->id();
|
||||
m_cut_contours.instance_idx = instance_idx;
|
||||
m_cut_contours.contours.reset();
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#if ENABLE_SINKING_CONTOURS
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#endif // ENABLE_SINKING_CONTOURS
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -33,7 +34,7 @@ class GLGizmoCut : public GLGizmoBase
|
|||
double cut_z{ 0.0 };
|
||||
Vec3d position{ Vec3d::Zero() };
|
||||
Vec3d shift{ Vec3d::Zero() };
|
||||
int object_idx{ -1 };
|
||||
ObjectID object_id;
|
||||
int instance_idx{ -1 };
|
||||
};
|
||||
|
||||
|
|
|
@ -67,8 +67,8 @@ void GLGizmoFdmSupports::render_painter_gizmo() const
|
|||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
|
|
@ -42,6 +42,8 @@ std::string GLGizmoFlatten::on_get_name() const
|
|||
|
||||
bool GLGizmoFlatten::on_is_activable() const
|
||||
{
|
||||
// This is assumed in GLCanvas3D::do_rotate, do not change this
|
||||
// without updating that function too.
|
||||
return m_parent.get_selection().is_single_full_instance();
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ void GLGizmoMmuSegmentation::on_shutdown()
|
|||
std::string GLGizmoMmuSegmentation::on_get_name() const
|
||||
{
|
||||
// FIXME Lukas H.: Discuss and change shortcut
|
||||
return (_L("MMU painting") + " [N]").ToUTF8().data();
|
||||
return (_L("Multimaterial painting") + " [N]").ToUTF8().data();
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_is_selectable() const
|
||||
|
@ -51,6 +51,11 @@ bool GLGizmoMmuSegmentation::on_is_selectable() const
|
|||
&& wxGetApp().get_mode() != comSimple && wxGetApp().extruders_edited_cnt() > 1);
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_is_activable() const
|
||||
{
|
||||
return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1;
|
||||
}
|
||||
|
||||
static std::vector<std::array<float, 4>> get_extruders_colors()
|
||||
{
|
||||
unsigned char rgb_color[3] = {};
|
||||
|
@ -142,6 +147,7 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const
|
|||
render_triangles(selection, false);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
@ -322,8 +328,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_brush);
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH))
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
|
@ -334,24 +345,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
}
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_bucket_fill + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_seed_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL;
|
||||
|
@ -369,6 +362,24 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_seed_fill + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(m_tool_type == ToolType::BRUSH) {
|
||||
|
@ -454,7 +465,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data());
|
||||
if(m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data()))
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
|
@ -581,10 +597,13 @@ std::array<float, 4> GLGizmoMmuSegmentation::get_cursor_sphere_right_button_colo
|
|||
return {color[0], color[1], color[2], 0.25f};
|
||||
}
|
||||
|
||||
static std::array<float, 4> get_seed_fill_color(const std::array<float, 4> &base_color)
|
||||
{
|
||||
return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f};
|
||||
}
|
||||
|
||||
void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
|
||||
{
|
||||
static constexpr std::array<float, 4> seed_fill_color{0.f, 1.f, 0.44f, 1.f};
|
||||
|
||||
if (m_update_render_data)
|
||||
update_render_data();
|
||||
|
||||
|
@ -597,12 +616,28 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
|
|||
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
|
||||
if (m_gizmo_scene.has_VBOs(color_idx)) {
|
||||
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color :
|
||||
color_idx == (m_gizmo_scene.triangle_indices.size() - 1) ? seed_fill_color :
|
||||
m_colors[color_idx - 1]);
|
||||
if (color_idx > m_colors.size()) // Seed fill VBO
|
||||
shader->set_uniform("uniform_color", get_seed_fill_color(color_idx == (m_colors.size() + 1) ? m_default_volume_color : m_colors[color_idx - (m_colors.size() + 1) - 1]));
|
||||
else // Normal VBO
|
||||
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color : m_colors[color_idx - 1]);
|
||||
|
||||
m_gizmo_scene.render(color_idx);
|
||||
}
|
||||
|
||||
if (m_gizmo_scene.has_contour_VBO()) {
|
||||
ScopeGuard guard_gouraud([shader]() { shader->start_using(); });
|
||||
shader->stop_using();
|
||||
|
||||
auto *contour_shader = wxGetApp().get_shader("mm_contour");
|
||||
contour_shader->start_using();
|
||||
|
||||
glsafe(::glDepthFunc(GL_LEQUAL));
|
||||
m_gizmo_scene.render_contour();
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
|
||||
contour_shader->stop_using();
|
||||
}
|
||||
|
||||
m_update_render_data = false;
|
||||
}
|
||||
|
||||
|
@ -619,10 +654,10 @@ void TriangleSelectorMmGui::update_render_data()
|
|||
|
||||
for (const Triangle &tr : m_triangles)
|
||||
if (tr.valid() && !tr.is_split()) {
|
||||
int color = int(tr.get_state());
|
||||
std::vector<int> &iva = tr.is_selected_by_seed_fill() ? m_gizmo_scene.triangle_indices.back() :
|
||||
color < int(m_gizmo_scene.triangle_indices.size() - 1) ? m_gizmo_scene.triangle_indices[color] :
|
||||
m_gizmo_scene.triangle_indices.front();
|
||||
int color = int(tr.get_state()) <= int(m_colors.size()) ? int(tr.get_state()) : 0;
|
||||
assert(m_colors.size() + 1 + color < m_gizmo_scene.triangle_indices.size());
|
||||
std::vector<int> &iva = m_gizmo_scene.triangle_indices[color + tr.is_selected_by_seed_fill() * (m_colors.size() + 1)];
|
||||
|
||||
if (iva.size() + 3 > iva.capacity())
|
||||
iva.reserve(next_highest_power_of_2(iva.size() + 3));
|
||||
|
||||
|
@ -635,6 +670,24 @@ void TriangleSelectorMmGui::update_render_data()
|
|||
m_gizmo_scene.triangle_indices_sizes[color_idx] = m_gizmo_scene.triangle_indices[color_idx].size();
|
||||
|
||||
m_gizmo_scene.finalize_triangle_indices();
|
||||
|
||||
std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
|
||||
m_gizmo_scene.contour_vertices.reserve(contour_edges.size() * 6);
|
||||
for (const Vec2i &edge : contour_edges) {
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.x());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.y());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.z());
|
||||
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.x());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.y());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.z());
|
||||
}
|
||||
|
||||
m_gizmo_scene.contour_indices.assign(m_gizmo_scene.contour_vertices.size() / 3, 0);
|
||||
std::iota(m_gizmo_scene.contour_indices.begin(), m_gizmo_scene.contour_indices.end(), 0);
|
||||
m_gizmo_scene.contour_indices_size = m_gizmo_scene.contour_indices.size();
|
||||
|
||||
m_gizmo_scene.finalize_contour();
|
||||
}
|
||||
|
||||
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
|
||||
|
@ -658,6 +711,14 @@ void GLMmSegmentationGizmo3DScene::release_geometry() {
|
|||
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
|
||||
triangle_indices_VBO_id = 0;
|
||||
}
|
||||
if (this->contour_vertices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->contour_vertices_VBO_id));
|
||||
this->contour_vertices_VBO_id = 0;
|
||||
}
|
||||
if (this->contour_indices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->contour_indices_VBO_id));
|
||||
this->contour_indices_VBO_id = 0;
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
|
@ -685,13 +746,36 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
|
|||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::render_contour() const
|
||||
{
|
||||
assert(this->contour_vertices_VBO_id != 0);
|
||||
assert(this->contour_indices_VBO_id != 0);
|
||||
|
||||
glsafe(::glLineWidth(4.0f));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_vertices_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
if (this->contour_indices_size > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices_VBO_id));
|
||||
glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_vertices()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
if (!this->vertices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->vertices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * 4, this->vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(float), this->vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->vertices.clear();
|
||||
}
|
||||
|
@ -706,19 +790,32 @@ void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
|
|||
if (!this->triangle_indices[buffer_idx].empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * 4, this->triangle_indices[buffer_idx].data(),
|
||||
GL_STATIC_DRAW));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * sizeof(int), this->triangle_indices[buffer_idx].data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->triangle_indices[buffer_idx].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_geometry()
|
||||
void GLMmSegmentationGizmo3DScene::finalize_contour()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
|
||||
finalize_vertices();
|
||||
finalize_triangle_indices();
|
||||
assert(this->contour_vertices_VBO_id == 0);
|
||||
assert(this->contour_indices_VBO_id == 0);
|
||||
|
||||
if (!this->contour_vertices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->contour_vertices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_vertices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->contour_vertices.clear();
|
||||
}
|
||||
|
||||
if (!this->contour_indices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->contour_indices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_indices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->contour_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -25,9 +25,8 @@ public:
|
|||
return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0;
|
||||
}
|
||||
|
||||
// Finalize the initialization of the geometry and indices, upload the geometry and indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_geometry();
|
||||
[[nodiscard]] inline bool has_contour_VBO() const { return this->contour_indices_VBO_id != 0; }
|
||||
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
|
||||
|
@ -36,6 +35,9 @@ public:
|
|||
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_triangle_indices();
|
||||
// Finalize the initialization of the contour geometry and the indices, upload both to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_contour();
|
||||
|
||||
void clear()
|
||||
{
|
||||
|
@ -45,28 +47,41 @@ public:
|
|||
|
||||
for (size_t &triangle_indices_size : this->triangle_indices_sizes)
|
||||
triangle_indices_size = 0;
|
||||
|
||||
this->contour_vertices.clear();
|
||||
this->contour_indices.clear();
|
||||
this->contour_indices_size = 0;
|
||||
}
|
||||
|
||||
void render(size_t triangle_indices_idx) const;
|
||||
|
||||
void render_contour() const;
|
||||
|
||||
std::vector<float> vertices;
|
||||
std::vector<std::vector<int>> triangle_indices;
|
||||
|
||||
std::vector<float> contour_vertices;
|
||||
std::vector<int> contour_indices;
|
||||
|
||||
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
|
||||
// the above mentioned std::vectors are cleared and the following variables keep their original length.
|
||||
std::vector<size_t> triangle_indices_sizes;
|
||||
size_t contour_indices_size{0};
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not sent to GPU yet.
|
||||
unsigned int vertices_VBO_id{0};
|
||||
std::vector<unsigned int> triangle_indices_VBO_ids;
|
||||
|
||||
unsigned int contour_vertices_VBO_id{0};
|
||||
unsigned int contour_indices_VBO_id{0};
|
||||
};
|
||||
|
||||
class TriangleSelectorMmGui : public TriangleSelectorGUI {
|
||||
public:
|
||||
// Plus 2 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the last position is allocated for seed fill.
|
||||
// Plus 1 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the indices above colors.size() are allocated for seed fill.
|
||||
explicit TriangleSelectorMmGui(const TriangleMesh &mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(colors.size() + 2) {}
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(2 * (colors.size() + 1)) {}
|
||||
~TriangleSelectorMmGui() override = default;
|
||||
|
||||
// Render current selection. Transformation matrices are supposed
|
||||
|
@ -109,6 +124,7 @@ protected:
|
|||
std::string on_get_name() const override;
|
||||
|
||||
bool on_is_selectable() const override;
|
||||
bool on_is_activable() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
|
|
|
@ -283,7 +283,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
return true;
|
||||
}
|
||||
else if (alt_down) {
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) {
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
|
||||
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
|
||||
m_parent.set_as_dirty();
|
||||
|
@ -292,6 +292,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin)
|
||||
: std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax);
|
||||
m_parent.set_as_dirty();
|
||||
if (m_rr.mesh_id != -1) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle, true);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -319,11 +324,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type();
|
||||
}
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
|
||||
const Transform3d& instance_trafo = mi->get_transformation().get_matrix();
|
||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
|
||||
const Transform3d &instance_trafo = mi->get_transformation().get_matrix();
|
||||
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
|
@ -382,6 +387,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
|
||||
if (m_tool_type == ToolType::SEED_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle, true);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true, true);
|
||||
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} else if (m_tool_type == ToolType::BRUSH)
|
||||
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
|
||||
|
@ -515,7 +527,7 @@ bool GLGizmoPainterBase::on_is_activable() const
|
|||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
|
||||
|| !selection.is_single_full_instance())
|
||||
|| !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple)
|
||||
return false;
|
||||
|
||||
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
|
||||
|
|
|
@ -114,7 +114,7 @@ protected:
|
|||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_seed_fill_angle = 0.f;
|
||||
float m_seed_fill_angle = 30.f;
|
||||
|
||||
static constexpr float SeedFillAngleMin = 0.0f;
|
||||
static constexpr float SeedFillAngleMax = 90.f;
|
||||
|
|
|
@ -497,9 +497,6 @@ void GLGizmoRotate3D::on_render()
|
|||
m_gizmos[Z].render();
|
||||
}
|
||||
|
||||
const char * GLGizmoRotate3D::RotoptimzeWindow::options[RotoptimizeJob::get_methods_count()];
|
||||
bool GLGizmoRotate3D::RotoptimzeWindow::options_valid = false;
|
||||
|
||||
GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
|
||||
State & state,
|
||||
const Alignment &alignment)
|
||||
|
@ -515,21 +512,26 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
|
|||
y = std::min(y, alignment.bottom_limit - win_h);
|
||||
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
|
||||
|
||||
ImGui::PushItemWidth(200.f);
|
||||
ImGui::PushItemWidth(300.f);
|
||||
|
||||
size_t methods_cnt = RotoptimizeJob::get_methods_count();
|
||||
if (!options_valid) {
|
||||
for (size_t i = 0; i < methods_cnt; ++i)
|
||||
options[i] = RotoptimizeJob::get_method_names()[i].c_str();
|
||||
if (ImGui::BeginCombo(_L("Choose goal").c_str(), RotoptimizeJob::get_method_name(state.method_id).c_str())) {
|
||||
for (size_t i = 0; i < RotoptimizeJob::get_methods_count(); ++i) {
|
||||
if (ImGui::Selectable(RotoptimizeJob::get_method_name(i).c_str())) {
|
||||
state.method_id = i;
|
||||
wxGetApp().app_config->set("sla_auto_rotate",
|
||||
"method_id",
|
||||
std::to_string(state.method_id));
|
||||
}
|
||||
|
||||
options_valid = true;
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(i).c_str());
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
int citem = state.method_id;
|
||||
if (ImGui::Combo(_L("Choose goal").c_str(), &citem, options, methods_cnt) ) {
|
||||
state.method_id = citem;
|
||||
wxGetApp().app_config->set("sla_auto_rotate", "method_id", std::to_string(state.method_id));
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", RotoptimizeJob::get_method_description(state.method_id).c_str());
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
|
|
|
@ -138,10 +138,6 @@ private:
|
|||
|
||||
class RotoptimzeWindow {
|
||||
ImGuiWrapper *m_imgui = nullptr;
|
||||
|
||||
static const char * options [];
|
||||
static bool options_valid;
|
||||
|
||||
public:
|
||||
|
||||
struct State {
|
||||
|
|
|
@ -63,6 +63,7 @@ void GLGizmoSeam::render_painter_gizmo() const
|
|||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
|
385
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
Normal file
385
src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp
Normal file
|
@ -0,0 +1,385 @@
|
|||
#include "GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/QuadricEdgeCollapse.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
|
||||
const std::string &icon_filename,
|
||||
unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, -1)
|
||||
, m_state(State::settings)
|
||||
, m_is_valid_result(false)
|
||||
, m_exist_preview(false)
|
||||
, m_progress(0)
|
||||
, m_volume(nullptr)
|
||||
, m_obj_index(0)
|
||||
, m_need_reload(false)
|
||||
|
||||
, tr_mesh_name(_u8L("Mesh name"))
|
||||
, tr_triangles(_u8L("Triangles"))
|
||||
, tr_preview(_u8L("Preview"))
|
||||
, tr_detail_level(_u8L("Detail level"))
|
||||
, tr_decimate_ratio(_u8L("Decimate ratio"))
|
||||
{}
|
||||
|
||||
GLGizmoSimplify::~GLGizmoSimplify() {
|
||||
m_state = State::canceling;
|
||||
if (m_worker.joinable()) m_worker.join();
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_init()
|
||||
{
|
||||
//m_grabbers.emplace_back();
|
||||
//m_shortcut_key = WXK_CONTROL_C;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GLGizmoSimplify::on_get_name() const
|
||||
{
|
||||
return (_L("Simplify")).ToUTF8().data();
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::on_render() {}
|
||||
void GLGizmoSimplify::on_render_for_picking() {}
|
||||
|
||||
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
create_gui_cfg();
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
int object_idx = selection.get_object_idx();
|
||||
ModelObject *obj = wxGetApp().plater()->model().objects[object_idx];
|
||||
ModelVolume *act_volume = obj->volumes.front();
|
||||
|
||||
// Check selection of new volume
|
||||
// Do not reselect object when processing
|
||||
if (act_volume != m_volume && m_state == State::settings) {
|
||||
bool change_window_position = (m_volume == nullptr);
|
||||
// select different model
|
||||
if (m_volume != nullptr && m_original_its.has_value()) {
|
||||
set_its(*m_original_its);
|
||||
}
|
||||
|
||||
m_obj_index = object_idx; // to remember correct object
|
||||
m_volume = act_volume;
|
||||
m_original_its = {};
|
||||
m_configuration.decimate_ratio = 50.; // default value
|
||||
m_configuration.fix_count_by_ratio(m_volume->mesh().its.indices.size());
|
||||
m_is_valid_result = false;
|
||||
m_exist_preview = false;
|
||||
|
||||
if (change_window_position) {
|
||||
ImVec2 pos = ImGui::GetMousePos();
|
||||
pos.x -= m_gui_cfg->window_offset_x;
|
||||
pos.y -= m_gui_cfg->window_offset_y;
|
||||
// minimal top left value
|
||||
ImVec2 tl(m_gui_cfg->window_padding, m_gui_cfg->window_padding);
|
||||
if (pos.x < tl.x) pos.x = tl.x;
|
||||
if (pos.y < tl.y) pos.y = tl.y;
|
||||
// maximal bottom right value
|
||||
auto parent_size = m_parent.get_canvas_size();
|
||||
ImVec2 br(
|
||||
parent_size.get_width() - (2 * m_gui_cfg->window_offset_x + m_gui_cfg->window_padding),
|
||||
parent_size.get_height() - (2 * m_gui_cfg->window_offset_y + m_gui_cfg->window_padding));
|
||||
if (pos.x > br.x) pos.x = br.x;
|
||||
if (pos.y > br.y) pos.y = br.y;
|
||||
ImGui::SetNextWindowPos(pos, ImGuiCond_Always);
|
||||
}
|
||||
}
|
||||
|
||||
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoCollapse;
|
||||
m_imgui->begin(on_get_name(), flag);
|
||||
|
||||
size_t triangle_count = m_volume->mesh().its.indices.size();
|
||||
// already reduced mesh
|
||||
if (m_original_its.has_value())
|
||||
triangle_count = m_original_its->indices.size();
|
||||
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_mesh_name + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
std::string name = m_volume->name;
|
||||
if (name.length() > m_gui_cfg->max_char_in_name)
|
||||
name = name.substr(0, m_gui_cfg->max_char_in_name - 3) + "...";
|
||||
m_imgui->text(name);
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_triangles + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
m_imgui->text(std::to_string(triangle_count));
|
||||
/*
|
||||
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, tr_preview + ":");
|
||||
ImGui::SameLine(m_gui_cfg->top_left_width);
|
||||
if (m_exist_preview) {
|
||||
m_imgui->text(std::to_string(m_volume->mesh().its.indices.size()));
|
||||
} else {
|
||||
m_imgui->text("---");
|
||||
}*/
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(ImGui::RadioButton("##use_error", !m_configuration.use_count)) {
|
||||
m_is_valid_result = false;
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
m_imgui->disabled_begin(m_configuration.use_count);
|
||||
ImGui::Text("%s", tr_detail_level.c_str());
|
||||
std::vector<std::string> reduce_captions = {
|
||||
static_cast<std::string>(_u8L("Extra high")),
|
||||
static_cast<std::string>(_u8L("High")),
|
||||
static_cast<std::string>(_u8L("Medium")),
|
||||
static_cast<std::string>(_u8L("Low")),
|
||||
static_cast<std::string>(_u8L("Extra low"))
|
||||
};
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
static int reduction = 2;
|
||||
if(ImGui::SliderInt("##ReductionLevel", &reduction, 0, 4, reduce_captions[reduction].c_str())) {
|
||||
m_is_valid_result = false;
|
||||
if (reduction < 0) reduction = 0;
|
||||
if (reduction > 4) reduction = 4;
|
||||
switch (reduction) {
|
||||
case 0: m_configuration.max_error = 1e-3f; break;
|
||||
case 1: m_configuration.max_error = 1e-2f; break;
|
||||
case 2: m_configuration.max_error = 0.1f; break;
|
||||
case 3: m_configuration.max_error = 0.5f; break;
|
||||
case 4: m_configuration.max_error = 1.f; break;
|
||||
}
|
||||
}
|
||||
m_imgui->disabled_end(); // !use_count
|
||||
|
||||
if (ImGui::RadioButton("##use_count", m_configuration.use_count)) {
|
||||
m_is_valid_result = false;
|
||||
m_configuration.use_count = !m_configuration.use_count;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
// show preview result triangle count (percent)
|
||||
if (m_need_reload && !m_configuration.use_count) {
|
||||
m_configuration.wanted_count = static_cast<uint32_t>(m_volume->mesh().its.indices.size());
|
||||
m_configuration.decimate_ratio =
|
||||
(1.0f - (m_configuration.wanted_count / (float) triangle_count)) * 100.f;
|
||||
}
|
||||
|
||||
m_imgui->disabled_begin(!m_configuration.use_count);
|
||||
ImGui::Text("%s", tr_decimate_ratio.c_str());
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
ImGui::SetNextItemWidth(m_gui_cfg->input_width);
|
||||
const char * format = (m_configuration.decimate_ratio > 10)? "%.0f %%":
|
||||
((m_configuration.decimate_ratio > 1)? "%.1f %%":"%.2f %%");
|
||||
if (ImGui::SliderFloat("##decimate_ratio", &m_configuration.decimate_ratio, 0.f, 100.f, format)) {
|
||||
m_is_valid_result = false;
|
||||
if (m_configuration.decimate_ratio < 0.f)
|
||||
m_configuration.decimate_ratio = 0.01f;
|
||||
if (m_configuration.decimate_ratio > 100.f)
|
||||
m_configuration.decimate_ratio = 100.f;
|
||||
m_configuration.fix_count_by_ratio(triangle_count);
|
||||
}
|
||||
|
||||
ImGui::NewLine();
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
ImGui::Text(_L("%d triangles").c_str(), m_configuration.wanted_count);
|
||||
m_imgui->disabled_end(); // use_count
|
||||
|
||||
if (m_state == State::settings) {
|
||||
if (m_imgui->button(_L("Cancel"))) {
|
||||
if (m_original_its.has_value()) {
|
||||
set_its(*m_original_its);
|
||||
m_state = State::close_on_end;
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
}
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
if (m_imgui->button(_L("Preview"))) {
|
||||
m_state = State::preview;
|
||||
// simplify but not aply on mesh
|
||||
process();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (m_imgui->button(_L("Apply"))) {
|
||||
if (!m_is_valid_result) {
|
||||
m_state = State::close_on_end;
|
||||
process();
|
||||
} else {
|
||||
// use preview and close
|
||||
if (m_exist_preview) {
|
||||
// fix hollowing, sla support points, modifiers, ...
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->changed_mesh(m_obj_index);
|
||||
}
|
||||
close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_imgui->disabled_begin(m_state == State::canceling);
|
||||
if (m_imgui->button(_L("Cancel"))) m_state = State::canceling;
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::SameLine(m_gui_cfg->bottom_left_width);
|
||||
// draw progress bar
|
||||
char buf[32];
|
||||
sprintf(buf, L("Process %d / 100"), m_progress);
|
||||
ImGui::ProgressBar(m_progress / 100., ImVec2(m_gui_cfg->input_width, 0.f), buf);
|
||||
}
|
||||
m_imgui->end();
|
||||
|
||||
if (m_need_reload) {
|
||||
m_need_reload = false;
|
||||
bool close_on_end = (m_state == State::close_on_end);
|
||||
// Reload visualization of mesh - change VBO, FBO on GPU
|
||||
m_parent.reload_scene(true);
|
||||
// set m_state must be before close() !!!
|
||||
m_state = State::settings;
|
||||
if (close_on_end) {
|
||||
// fix hollowing, sla support points, modifiers, ...
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->changed_mesh(m_obj_index);
|
||||
close();
|
||||
}
|
||||
|
||||
// Fix warning icon in object list
|
||||
wxGetApp().obj_list()->update_item_error_icon(m_obj_index, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::close() {
|
||||
// close gizmo == open it again
|
||||
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
|
||||
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoSimplify::process()
|
||||
{
|
||||
class SimplifyCanceledException : public std::exception {
|
||||
public:
|
||||
const char* what() const throw() { return L("Model simplification has been canceled"); }
|
||||
};
|
||||
|
||||
if (!m_original_its.has_value())
|
||||
m_original_its = m_volume->mesh().its; // copy
|
||||
|
||||
auto plater = wxGetApp().plater();
|
||||
plater->take_snapshot(_L("Simplify ") + m_volume->name);
|
||||
plater->clear_before_change_mesh(m_obj_index);
|
||||
m_progress = 0;
|
||||
if (m_worker.joinable()) m_worker.join();
|
||||
m_worker = std::thread([this]() {
|
||||
// store original triangles
|
||||
uint32_t triangle_count = (m_configuration.use_count) ? m_configuration.wanted_count : 0;
|
||||
float max_error = (!m_configuration.use_count) ? m_configuration.max_error : std::numeric_limits<float>::max();
|
||||
|
||||
std::function<void(void)> throw_on_cancel = [&]() {
|
||||
if (m_state == State::canceling) {
|
||||
throw SimplifyCanceledException();
|
||||
}
|
||||
};
|
||||
|
||||
std::function<void(int)> statusfn = [this](int percent) {
|
||||
m_progress = percent;
|
||||
|
||||
// check max 4fps
|
||||
static int64_t last = 0;
|
||||
int64_t now = m_parent.timestamp_now();
|
||||
if ((now - last) < 250) return;
|
||||
last = now;
|
||||
|
||||
request_rerender();
|
||||
};
|
||||
|
||||
indexed_triangle_set collapsed = *m_original_its; // copy
|
||||
|
||||
try {
|
||||
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
|
||||
set_its(collapsed);
|
||||
m_is_valid_result = true;
|
||||
m_exist_preview = true;
|
||||
} catch (SimplifyCanceledException &) {
|
||||
// set state out of main thread
|
||||
m_state = State::settings;
|
||||
}
|
||||
// need to render last status fn to change bar graph to buttons
|
||||
request_rerender();
|
||||
});
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
|
||||
auto tm = std::make_unique<TriangleMesh>(its);
|
||||
tm->repair();
|
||||
m_volume->set_mesh(std::move(tm));
|
||||
m_volume->set_new_unique_id();
|
||||
m_volume->get_object()->invalidate_bounding_box();
|
||||
m_need_reload = true;
|
||||
}
|
||||
|
||||
bool GLGizmoSimplify::on_is_activable() const
|
||||
{
|
||||
return !m_parent.get_selection().is_empty();
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::on_set_state()
|
||||
{
|
||||
// Closing gizmo. e.g. selecting another one
|
||||
if (GLGizmoBase::m_state == GLGizmoBase::Off) {
|
||||
|
||||
// refuse outgoing during simlification
|
||||
if (m_state != State::settings) {
|
||||
GLGizmoBase::m_state = GLGizmoBase::On;
|
||||
auto notification_manager = wxGetApp().plater()->get_notification_manager();
|
||||
notification_manager->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("ERROR: Wait until Simplification ends or Cancel process."));
|
||||
return;
|
||||
}
|
||||
|
||||
// revert preview
|
||||
if (m_exist_preview) {
|
||||
set_its(*m_original_its);
|
||||
m_parent.reload_scene(true);
|
||||
m_need_reload = false;
|
||||
}
|
||||
|
||||
// invalidate selected model
|
||||
m_volume = nullptr;
|
||||
} else if (GLGizmoBase::m_state == GLGizmoBase::On) {
|
||||
// when open by hyperlink it needs to show up
|
||||
request_rerender();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::create_gui_cfg() {
|
||||
if (m_gui_cfg.has_value()) return;
|
||||
int space_size = m_imgui->calc_text_size(":MM").x;
|
||||
GuiCfg cfg;
|
||||
cfg.top_left_width = std::max(m_imgui->calc_text_size(tr_mesh_name).x,
|
||||
m_imgui->calc_text_size(tr_triangles).x)
|
||||
+ space_size;
|
||||
|
||||
const float radio_size = ImGui::GetFrameHeight();
|
||||
cfg.bottom_left_width =
|
||||
std::max(m_imgui->calc_text_size(tr_detail_level).x,
|
||||
m_imgui->calc_text_size(tr_decimate_ratio).x) +
|
||||
space_size + radio_size;
|
||||
|
||||
cfg.input_width = cfg.bottom_left_width * 1.5;
|
||||
cfg.window_offset_x = (cfg.bottom_left_width + cfg.input_width)/2;
|
||||
cfg.window_offset_y = ImGui::GetTextLineHeightWithSpacing() * 5;
|
||||
m_gui_cfg = cfg;
|
||||
}
|
||||
|
||||
void GLGizmoSimplify::request_rerender() {
|
||||
wxGetApp().plater()->CallAfter([this]() {
|
||||
set_dirty();
|
||||
m_parent.schedule_extra_frame(0);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
106
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
Normal file
106
src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
#ifndef slic3r_GLGizmoSimplify_hpp_
|
||||
#define slic3r_GLGizmoSimplify_hpp_
|
||||
|
||||
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
|
||||
// which overrides our localization "L" macro.
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "admesh/stl.h" // indexed_triangle_set
|
||||
#include <thread>
|
||||
#include <optional>
|
||||
#include <atomic>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelVolume;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
class GLGizmoSimplify : public GLGizmoBase
|
||||
{
|
||||
public:
|
||||
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
virtual ~GLGizmoSimplify();
|
||||
protected:
|
||||
virtual bool on_init() override;
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual void on_render() override;
|
||||
virtual void on_render_for_picking() override;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
virtual bool on_is_activable() const override;
|
||||
virtual bool on_is_selectable() const override { return false; }
|
||||
virtual void on_set_state() override;
|
||||
|
||||
private:
|
||||
void close();
|
||||
void process();
|
||||
void set_its(indexed_triangle_set &its);
|
||||
void create_gui_cfg();
|
||||
void request_rerender();
|
||||
|
||||
std::atomic_bool m_is_valid_result; // differ what to do in apply
|
||||
std::atomic_bool m_exist_preview; // set when process end
|
||||
|
||||
volatile int m_progress; // percent of done work
|
||||
ModelVolume *m_volume; //
|
||||
size_t m_obj_index;
|
||||
|
||||
std::optional<indexed_triangle_set> m_original_its;
|
||||
|
||||
volatile bool m_need_reload; // after simplify, glReload must be on main thread
|
||||
std::thread m_worker;
|
||||
|
||||
enum class State {
|
||||
settings,
|
||||
preview, // simplify to show preview
|
||||
close_on_end, // simplify with close on end
|
||||
canceling // after button click, before canceled
|
||||
};
|
||||
volatile State m_state;
|
||||
|
||||
struct Configuration
|
||||
{
|
||||
bool use_count = false;
|
||||
// minimal triangle count
|
||||
float decimate_ratio = 50.f; // in percent
|
||||
uint32_t wanted_count = 0; // initialize by percents
|
||||
|
||||
// maximal quadric error
|
||||
float max_error = 1.;
|
||||
|
||||
void fix_count_by_ratio(size_t triangle_count)
|
||||
{
|
||||
wanted_count = static_cast<uint32_t>(
|
||||
std::round(triangle_count * (100.f-decimate_ratio) / 100.f));
|
||||
}
|
||||
} m_configuration;
|
||||
|
||||
// This configs holds GUI layout size given by translated texts.
|
||||
// etc. When language changes, GUI is recreated and this class constructed again,
|
||||
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
|
||||
struct GuiCfg
|
||||
{
|
||||
int top_left_width = 100;
|
||||
int bottom_left_width = 100;
|
||||
int input_width = 100;
|
||||
int window_offset_x = 100;
|
||||
int window_offset_y = 100;
|
||||
int window_padding = 0;
|
||||
|
||||
// trunc model name when longer
|
||||
size_t max_char_in_name = 30;
|
||||
};
|
||||
std::optional<GuiCfg> m_gui_cfg;
|
||||
|
||||
// translations used for calc window size
|
||||
const std::string tr_mesh_name;
|
||||
const std::string tr_triangles;
|
||||
const std::string tr_preview;
|
||||
const std::string tr_detail_level;
|
||||
const std::string tr_decimate_ratio;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoSimplify_hpp_
|
|
@ -447,7 +447,8 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
}
|
||||
|
||||
if (action == SLAGizmoEventType::DiscardChanges) {
|
||||
editing_mode_discard_changes();
|
||||
ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
|
||||
[this](){ editing_mode_discard_changes(); });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -879,6 +880,22 @@ CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const
|
|||
|
||||
|
||||
|
||||
void GLGizmoSlaSupports::ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no)
|
||||
{
|
||||
wxGetApp().CallAfter([this, on_yes, on_no]() {
|
||||
// Following is called through CallAfter, because otherwise there was a problem
|
||||
// on OSX with the wxMessageDialog being shown several times when clicked into.
|
||||
MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
|
||||
"edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
|
||||
int ret = dlg.ShowModal();
|
||||
if (ret == wxID_YES)
|
||||
on_yes();
|
||||
else if (ret == wxID_NO)
|
||||
on_no();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoSlaSupports::on_set_state()
|
||||
{
|
||||
if (m_state == m_old_state)
|
||||
|
@ -901,17 +918,8 @@ void GLGizmoSlaSupports::on_set_state()
|
|||
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
|
||||
bool will_ask = m_editing_mode && unsaved_changes() && on_is_activable();
|
||||
if (will_ask) {
|
||||
wxGetApp().CallAfter([this]() {
|
||||
// Following is called through CallAfter, because otherwise there was a problem
|
||||
// on OSX with the wxMessageDialog being shown several times when clicked into.
|
||||
//wxMessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
|
||||
MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
|
||||
"edited support points?") + "\n",_L("Save changes?"), wxICON_QUESTION | wxYES | wxNO);
|
||||
if (dlg.ShowModal() == wxID_YES)
|
||||
editing_mode_apply_changes();
|
||||
else
|
||||
editing_mode_discard_changes();
|
||||
});
|
||||
ask_about_changes_call_after([this](){ editing_mode_apply_changes(); },
|
||||
[this](){ editing_mode_discard_changes(); });
|
||||
// refuse to be turned off so the gizmo is active when the CallAfter is executed
|
||||
m_state = m_old_state;
|
||||
}
|
||||
|
|
|
@ -117,6 +117,7 @@ private:
|
|||
void auto_generate();
|
||||
void switch_to_editing_mode();
|
||||
void disable_editing_mode();
|
||||
void ask_about_changes_call_after(std::function<void()> on_yes, std::function<void()> on_no);
|
||||
|
||||
protected:
|
||||
void on_set_state() override;
|
||||
|
|
|
@ -150,6 +150,30 @@ void InstancesHider::on_update()
|
|||
canvas->toggle_model_objects_visibility(false);
|
||||
canvas->toggle_model_objects_visibility(true, mo, active_inst);
|
||||
canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst);
|
||||
canvas->set_use_clipping_planes(true);
|
||||
// Some objects may be sinking, do not show whatever is below the bed.
|
||||
canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), 0.));
|
||||
canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits<double>::max()));
|
||||
|
||||
|
||||
std::vector<const TriangleMesh*> meshes;
|
||||
for (const ModelVolume* mv : mo->volumes)
|
||||
meshes.push_back(&mv->mesh());
|
||||
|
||||
if (meshes != m_old_meshes) {
|
||||
m_clippers.clear();
|
||||
for (const TriangleMesh* mesh : meshes) {
|
||||
m_clippers.emplace_back(new MeshClipper);
|
||||
if (mo->get_instance_min_z(active_inst) < SINKING_Z_THRESHOLD)
|
||||
m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), 0.));
|
||||
else {
|
||||
m_clippers.back()->set_plane(ClippingPlane::ClipsNothing());
|
||||
m_clippers.back()->set_limiting_plane(ClippingPlane::ClipsNothing());
|
||||
}
|
||||
m_clippers.back()->set_mesh(*mesh);
|
||||
}
|
||||
m_old_meshes = meshes;
|
||||
}
|
||||
}
|
||||
else
|
||||
canvas->toggle_model_objects_visibility(true);
|
||||
|
@ -158,6 +182,9 @@ void InstancesHider::on_update()
|
|||
void InstancesHider::on_release()
|
||||
{
|
||||
get_pool()->get_canvas()->toggle_model_objects_visibility(true);
|
||||
get_pool()->get_canvas()->set_use_clipping_planes(false);
|
||||
m_old_meshes.clear();
|
||||
m_clippers.clear();
|
||||
}
|
||||
|
||||
void InstancesHider::show_supports(bool show) {
|
||||
|
@ -167,6 +194,38 @@ void InstancesHider::show_supports(bool show) {
|
|||
}
|
||||
}
|
||||
|
||||
void InstancesHider::render_cut() const
|
||||
{
|
||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
const ModelObject* mo = sel_info->model_object();
|
||||
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
|
||||
|
||||
size_t clipper_id = 0;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
Geometry::Transformation vol_trafo = mv->get_transformation();
|
||||
Geometry::Transformation trafo = inst_trafo * vol_trafo;
|
||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
||||
|
||||
auto& clipper = m_clippers[clipper_id];
|
||||
clipper->set_transformation(trafo);
|
||||
const ObjectClipper* obj_clipper = get_pool()->object_clipper();
|
||||
if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane()
|
||||
&& obj_clipper->get_position() != 0.) {
|
||||
ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane();
|
||||
clp.set_normal(-clp.get_normal());
|
||||
clipper->set_limiting_plane(clp);
|
||||
} else
|
||||
clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
|
||||
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(0.8f, 0.3f, 0.0f));
|
||||
clipper->render_cut();
|
||||
glsafe(::glPopMatrix());
|
||||
|
||||
++clipper_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HollowedMesh::on_update()
|
||||
|
@ -348,6 +407,7 @@ void ObjectClipper::render_cut() const
|
|||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
const ModelObject* mo = sel_info->model_object();
|
||||
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
|
||||
const bool sinking = mo->bounding_box().min.z() < SINKING_Z_THRESHOLD;
|
||||
|
||||
size_t clipper_id = 0;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
|
@ -358,7 +418,9 @@ void ObjectClipper::render_cut() const
|
|||
auto& clipper = m_clippers[clipper_id];
|
||||
clipper->set_plane(*m_clp);
|
||||
clipper->set_transformation(trafo);
|
||||
|
||||
clipper->set_limiting_plane(sinking ?
|
||||
ClippingPlane(Vec3d::UnitZ(), 0.)
|
||||
: ClippingPlane::ClipsNothing());
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
|
||||
clipper->render_cut();
|
||||
|
|
|
@ -180,6 +180,7 @@ public:
|
|||
|
||||
void show_supports(bool show);
|
||||
bool are_supports_shown() const { return m_show_supports; }
|
||||
void render_cut() const;
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
|
@ -187,6 +188,8 @@ protected:
|
|||
|
||||
private:
|
||||
bool m_show_supports = false;
|
||||
std::vector<const TriangleMesh*> m_old_meshes;
|
||||
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
@ -50,14 +51,7 @@ std::vector<size_t> GLGizmosManager::get_selectable_idxs() const
|
|||
return out;
|
||||
}
|
||||
|
||||
std::vector<size_t> GLGizmosManager::get_activable_idxs() const
|
||||
{
|
||||
std::vector<size_t> out;
|
||||
for (size_t i=0; i<m_gizmos.size(); ++i)
|
||||
if (m_gizmos[i]->is_activable())
|
||||
out.push_back(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const
|
||||
{
|
||||
|
@ -109,7 +103,8 @@ bool GLGizmosManager::init()
|
|||
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
|
||||
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
|
||||
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "fdm_supports.svg", 9));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
|
||||
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "cut.svg", 10));
|
||||
|
||||
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
|
||||
|
||||
|
@ -137,8 +132,7 @@ bool GLGizmosManager::init_arrow(const BackgroundTexture::Metadata& arrow_textur
|
|||
bool res = false;
|
||||
|
||||
if (!arrow_texture.filename.empty())
|
||||
res = m_arrow_texture.texture.load_from_file(path + arrow_texture.filename, false, GLTexture::SingleThreaded, false);
|
||||
// res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, true, false, 100);
|
||||
res = m_arrow_texture.texture.load_from_svg_file(path + arrow_texture.filename, false, false, false, 1000);
|
||||
if (res)
|
||||
m_arrow_texture.metadata = arrow_texture;
|
||||
|
||||
|
@ -169,7 +163,7 @@ void GLGizmosManager::refresh_on_off_state()
|
|||
return;
|
||||
|
||||
if (m_current != Undefined
|
||||
&& (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) {
|
||||
&& ! m_gizmos[m_current]->is_activable()) {
|
||||
activate_gizmo(Undefined);
|
||||
update_data();
|
||||
}
|
||||
|
@ -187,7 +181,7 @@ void GLGizmosManager::reset_all_states()
|
|||
bool GLGizmosManager::open_gizmo(EType type)
|
||||
{
|
||||
int idx = int(type);
|
||||
if (m_gizmos[idx]->is_selectable() && m_gizmos[idx]->is_activable()) {
|
||||
if (m_gizmos[idx]->is_activable()) {
|
||||
activate_gizmo(m_current == idx ? Undefined : (EType)idx);
|
||||
update_data();
|
||||
return true;
|
||||
|
@ -304,7 +298,7 @@ bool GLGizmosManager::handle_shortcut(int key)
|
|||
auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(),
|
||||
[key](const std::unique_ptr<GLGizmoBase>& gizmo) {
|
||||
int gizmo_key = gizmo->get_shortcut_key();
|
||||
return gizmo->is_selectable()
|
||||
return gizmo->is_activable()
|
||||
&& ((gizmo_key == key - 64) || (gizmo_key == key - 96));
|
||||
});
|
||||
|
||||
|
@ -1024,7 +1018,7 @@ void GLGizmosManager::render_arrow(const GLCanvas3D& parent, EType highlighted_t
|
|||
|
||||
float arrow_sides_ratio = (float)m_arrow_texture.texture.get_height() / (float)m_arrow_texture.texture.get_width();
|
||||
|
||||
GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { internal_left_uv, internal_top_uv }, { internal_left_uv, internal_bottom_uv }, { internal_right_uv, internal_bottom_uv }, { internal_right_uv, internal_top_uv } });
|
||||
GLTexture::render_sub_texture(tex_id, zoomed_top_x + zoomed_icons_size * 1.2f, zoomed_top_x + zoomed_icons_size * 1.2f + zoomed_icons_size * 2.2f * arrow_sides_ratio, zoomed_top_y - zoomed_icons_size * 1.6f , zoomed_top_y + zoomed_icons_size * 0.6f, { { internal_left_uv, internal_bottom_uv }, { internal_left_uv, internal_top_uv }, { internal_right_uv, internal_top_uv }, { internal_right_uv, internal_bottom_uv } });
|
||||
break;
|
||||
}
|
||||
zoomed_top_y -= zoomed_stride_y;
|
||||
|
@ -1077,6 +1071,7 @@ void GLGizmosManager::do_render_overlay() const
|
|||
float u_offset = 1.0f / (float)tex_width;
|
||||
float v_offset = 1.0f / (float)tex_height;
|
||||
|
||||
float current_y = FLT_MAX;
|
||||
for (size_t idx : selectable_idxs)
|
||||
{
|
||||
GLGizmoBase* gizmo = m_gizmos[idx].get();
|
||||
|
@ -1090,12 +1085,18 @@ void GLGizmosManager::do_render_overlay() const
|
|||
float u_right = u_left + du - u_offset;
|
||||
|
||||
GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
|
||||
if (idx == m_current) {
|
||||
float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height();
|
||||
gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top);
|
||||
if (idx == m_current || current_y == FLT_MAX) {
|
||||
// The FLT_MAX trick is here so that even non-selectable but activable
|
||||
// gizmos are passed some meaningful value.
|
||||
current_y = 0.5f * cnv_h - zoomed_top_y * zoom;
|
||||
}
|
||||
zoomed_top_y -= zoomed_stride_y;
|
||||
}
|
||||
|
||||
if (m_current != Undefined) {
|
||||
float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height();
|
||||
m_gizmos[m_current]->render_input_window(width, current_y, toolbar_top);
|
||||
}
|
||||
}
|
||||
|
||||
float GLGizmosManager::get_scaled_total_height() const
|
||||
|
@ -1246,6 +1247,14 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const
|
|||
}
|
||||
|
||||
|
||||
bool GLGizmosManager::is_hiding_instances() const
|
||||
{
|
||||
return (m_common_gizmos_data
|
||||
&& m_common_gizmos_data->instances_hider()
|
||||
&& m_common_gizmos_data->instances_hider()->is_valid());
|
||||
}
|
||||
|
||||
|
||||
int GLGizmosManager::get_shortcut_key(GLGizmosManager::EType type) const
|
||||
{
|
||||
return m_gizmos[type]->get_shortcut_key();
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
FdmSupports,
|
||||
Seam,
|
||||
MmuSegmentation,
|
||||
Simplify,
|
||||
Undefined
|
||||
};
|
||||
|
||||
|
@ -101,7 +102,6 @@ private:
|
|||
std::pair<EType, bool> m_highlight; // bool true = higlightedShown, false = highlightedHidden
|
||||
|
||||
std::vector<size_t> get_selectable_idxs() const;
|
||||
std::vector<size_t> get_activable_idxs() const;
|
||||
size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const;
|
||||
|
||||
void activate_gizmo(EType type);
|
||||
|
@ -122,7 +122,6 @@ private:
|
|||
MouseCapture m_mouse_capture;
|
||||
std::string m_tooltip;
|
||||
bool m_serializing;
|
||||
//std::unique_ptr<CommonGizmosData> m_common_gizmos_data;
|
||||
std::unique_ptr<CommonGizmosDataPool> m_common_gizmos_data;
|
||||
|
||||
public:
|
||||
|
@ -218,6 +217,7 @@ public:
|
|||
bool wants_reslice_supports_on_undo() const;
|
||||
|
||||
bool is_in_editing_mode(bool error_notification = false) const;
|
||||
bool is_hiding_instances() const;
|
||||
|
||||
void render_current_gizmo() const;
|
||||
void render_current_gizmo_for_picking_pass() const;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
@ -13,6 +14,31 @@
|
|||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <map>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
#define HINTS_CEREAL_VERSION 1
|
||||
// structure for writing used hints into binary file with version
|
||||
struct HintsCerealData
|
||||
{
|
||||
std::vector<std::string> my_data;
|
||||
// cereal will supply the version automatically when loading or saving
|
||||
// The version number comes from the CEREAL_CLASS_VERSION macro
|
||||
template<class Archive>
|
||||
void serialize(Archive& ar, std::uint32_t const version)
|
||||
{
|
||||
// You can choose different behaviors depending on the version
|
||||
// This is useful if you need to support older variants of your codebase
|
||||
// interacting with newer ones
|
||||
if (version > HINTS_CEREAL_VERSION)
|
||||
throw Slic3r::IOError("Version of hints.cereal is higher than current version.");
|
||||
else
|
||||
ar(my_data);
|
||||
}
|
||||
};
|
||||
// version of used hints binary file
|
||||
CEREAL_CLASS_VERSION(HintsCerealData, HINTS_CEREAL_VERSION);
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
@ -30,45 +56,199 @@ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, f
|
|||
else
|
||||
ImGui::PushStyleColor(idx, col);
|
||||
}
|
||||
// return true if NOT in disabled mode.
|
||||
inline bool disabled_modes_check(const std::string& disabled_modes)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void write_used_binary(const std::vector<std::string>& ids)
|
||||
{
|
||||
if (disabled_modes.empty())
|
||||
boost::filesystem::ofstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"), std::ios::binary);
|
||||
cereal::BinaryOutputArchive archive(file);
|
||||
HintsCerealData cd { ids };
|
||||
try
|
||||
{
|
||||
archive(cd);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to write to hints.cereal. " << ex.what();
|
||||
}
|
||||
}
|
||||
void read_used_binary(std::vector<std::string>& ids)
|
||||
{
|
||||
boost::filesystem::ifstream file((boost::filesystem::path(data_dir()) / "cache" / "hints.cereal"));
|
||||
cereal::BinaryInputArchive archive(file);
|
||||
HintsCerealData cd;
|
||||
try
|
||||
{
|
||||
archive(cd);
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to load to hints.cereal. " << ex.what();
|
||||
return;
|
||||
}
|
||||
ids = cd.my_data;
|
||||
}
|
||||
enum TagCheckResult
|
||||
{
|
||||
TagCheckAffirmative,
|
||||
TagCheckNegative,
|
||||
TagCheckNotCompatible
|
||||
};
|
||||
// returns if in mode defined by tag
|
||||
TagCheckResult tag_check_mode(const std::string& tag)
|
||||
{
|
||||
std::vector<std::string> allowed_tags = {"simple", "advanced", "expert"};
|
||||
if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end())
|
||||
{
|
||||
ConfigOptionMode config_mode = wxGetApp().get_mode();
|
||||
if (config_mode == ConfigOptionMode::comSimple) return (tag == "simple" ? TagCheckAffirmative : TagCheckNegative);
|
||||
else if (config_mode == ConfigOptionMode::comAdvanced) return (tag == "advanced" ? TagCheckAffirmative : TagCheckNegative);
|
||||
else if (config_mode == ConfigOptionMode::comExpert) return (tag == "expert" ? TagCheckAffirmative : TagCheckNegative);
|
||||
}
|
||||
return TagCheckNotCompatible;
|
||||
}
|
||||
|
||||
TagCheckResult tag_check_tech(const std::string& tag)
|
||||
{
|
||||
std::vector<std::string> allowed_tags = { "FFF", "MMU", "SLA" };
|
||||
if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end()) {
|
||||
const PrinterTechnology tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
if (tech == ptFFF) {
|
||||
// MMU / FFF
|
||||
bool is_mmu = wxGetApp().extruders_edited_cnt() > 1;
|
||||
if (tag == "MMU") return (is_mmu ? TagCheckAffirmative : TagCheckNegative);
|
||||
return (tag == "FFF" ? TagCheckAffirmative : TagCheckNegative);
|
||||
} else {
|
||||
// SLA
|
||||
return (tag == "SLA" ? TagCheckAffirmative : TagCheckNegative);
|
||||
}
|
||||
}
|
||||
return TagCheckNotCompatible;
|
||||
}
|
||||
|
||||
TagCheckResult tag_check_system(const std::string& tag)
|
||||
{
|
||||
std::vector<std::string> allowed_tags = { "Windows", "Linux", "OSX" };
|
||||
if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end()) {
|
||||
if (tag =="Windows")
|
||||
#ifdef WIN32
|
||||
return TagCheckAffirmative;
|
||||
#else
|
||||
return TagCheckNegative;
|
||||
#endif // WIN32
|
||||
|
||||
if (tag == "Linux")
|
||||
#ifdef __linux__
|
||||
return TagCheckAffirmative;
|
||||
#else
|
||||
return TagCheckNegative;
|
||||
#endif // __linux__
|
||||
|
||||
if (tag == "OSX")
|
||||
#ifdef __APPLE__
|
||||
return TagCheckAffirmative;
|
||||
#else
|
||||
return TagCheckNegative;
|
||||
#endif // __apple__
|
||||
}
|
||||
return TagCheckNotCompatible;
|
||||
}
|
||||
|
||||
// return true if NOT in disabled mode.
|
||||
bool tags_check(const std::string& disabled_tags, const std::string& enabled_tags)
|
||||
{
|
||||
if (disabled_tags.empty() && enabled_tags.empty())
|
||||
return true;
|
||||
|
||||
// simple / advanced / expert
|
||||
ConfigOptionMode config_mode = wxGetApp().get_mode();
|
||||
std::string mode_name;
|
||||
if (config_mode == ConfigOptionMode::comSimple) mode_name = "simple";
|
||||
else if (config_mode == ConfigOptionMode::comAdvanced) mode_name = "advanced";
|
||||
else if (config_mode == ConfigOptionMode::comExpert) mode_name = "expert";
|
||||
|
||||
if (!mode_name.empty() && disabled_modes.find(mode_name) != std::string::npos)
|
||||
return false;
|
||||
|
||||
// fff / sla
|
||||
const PrinterTechnology tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
if (tech == ptFFF) {
|
||||
if (disabled_modes.find("FFF") != std::string::npos)
|
||||
return false;
|
||||
} else {
|
||||
if (disabled_modes.find("SLA") != std::string::npos)
|
||||
return false;
|
||||
}
|
||||
|
||||
// enabled tags must ALL return affirmative or check fails
|
||||
if (!enabled_tags.empty()) {
|
||||
std::string tag;
|
||||
for (size_t i = 0; i < enabled_tags.size(); i++) {
|
||||
if (enabled_tags[i] == ' ') {
|
||||
tag.erase();
|
||||
continue;
|
||||
}
|
||||
if (enabled_tags[i] != ';') {
|
||||
tag += enabled_tags[i];
|
||||
}
|
||||
if (enabled_tags[i] == ';' || i == enabled_tags.size() - 1) {
|
||||
if (!tag.empty()) {
|
||||
TagCheckResult result;
|
||||
result = tag_check_mode(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
continue;
|
||||
result = tag_check_tech(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
continue;
|
||||
result = tag_check_system(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint Notification: Tag " << tag << " in enabled_tags not compatible.";
|
||||
// non compatible in enabled means return false since all enabled must be affirmative.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// disabled tags must all NOT return affirmative or check fails
|
||||
if (!disabled_tags.empty()) {
|
||||
std::string tag;
|
||||
for (size_t i = 0; i < disabled_tags.size(); i++) {
|
||||
if (disabled_tags[i] == ' ') {
|
||||
tag.erase();
|
||||
continue;
|
||||
}
|
||||
if (disabled_tags[i] != ';') {
|
||||
tag += disabled_tags[i];
|
||||
}
|
||||
if (disabled_tags[i] == ';' || i == disabled_tags.size() - 1) {
|
||||
if (!tag.empty()) {
|
||||
TagCheckResult result;
|
||||
result = tag_check_mode(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
continue;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
return false;
|
||||
result = tag_check_tech(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
continue;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
return false;
|
||||
result = tag_check_system(tag);
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint Notification: Tag " << tag << " in disabled_tags not compatible.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void launch_browser_if_allowed(const std::string& url)
|
||||
{
|
||||
wxGetApp().open_browser_with_warning_dialog(url);
|
||||
}
|
||||
} //namespace
|
||||
|
||||
HintDatabase::~HintDatabase()
|
||||
{
|
||||
if (m_initialized) {
|
||||
write_used_binary(m_used_ids);
|
||||
}
|
||||
}
|
||||
void HintDatabase::init()
|
||||
{
|
||||
|
||||
load_hints_from_file(std::move(boost::filesystem::path(resources_dir()) / "data" / "hints.ini"));
|
||||
|
||||
const AppConfig* app_config = wxGetApp().app_config;
|
||||
m_hint_id = std::atoi(app_config->get("last_hint").c_str());
|
||||
m_initialized = true;
|
||||
|
||||
}
|
||||
void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
||||
{
|
||||
|
@ -89,14 +269,23 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
|||
for (const auto& data : section.second) {
|
||||
dict.emplace(data.first, data.second.data());
|
||||
}
|
||||
|
||||
//unescaping a translating all texts
|
||||
//unescape text1
|
||||
// unique id string [hint:id] (trim "hint:")
|
||||
std::string id_string = section.first.substr(5);
|
||||
id_string = std::to_string(std::hash<std::string>{}(id_string));
|
||||
// unescaping and translating all texts and saving all data common for all hint types
|
||||
std::string fulltext;
|
||||
std::string text1;
|
||||
std::string hypertext_text;
|
||||
std::string follow_text;
|
||||
std::string disabled_modes;
|
||||
// tags
|
||||
std::string disabled_tags;
|
||||
std::string enabled_tags;
|
||||
// optional link to documentation (accessed from button)
|
||||
std::string documentation_link;
|
||||
// randomized weighted order variables
|
||||
size_t weight = 1;
|
||||
bool was_displayed = is_used(id_string);
|
||||
//unescape text1
|
||||
unescape_string_cstyle(_utf8(dict["text"]), fulltext);
|
||||
// replace <b> and </b> for imgui markers
|
||||
std::string marker_s(1, ImGui::ColorMarkerStart);
|
||||
|
@ -143,8 +332,18 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
|||
text1 = fulltext;
|
||||
}
|
||||
|
||||
if (dict.find("disabled_modes") != dict.end()) {
|
||||
disabled_modes = dict["disabled_modes"];
|
||||
if (dict.find("disabled_tags") != dict.end()) {
|
||||
disabled_tags = dict["disabled_tags"];
|
||||
}
|
||||
if (dict.find("enabled_tags") != dict.end()) {
|
||||
enabled_tags = dict["enabled_tags"];
|
||||
}
|
||||
if (dict.find("documentation_link") != dict.end()) {
|
||||
documentation_link = dict["documentation_link"];
|
||||
}
|
||||
|
||||
if (dict.find("weight") != dict.end()) {
|
||||
weight = (size_t)std::max(1, std::atoi(dict["weight"].c_str()));
|
||||
}
|
||||
|
||||
// create HintData
|
||||
|
@ -152,65 +351,148 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
|||
//link to internet
|
||||
if(dict["hypertext_type"] == "link") {
|
||||
std::string hypertext_link = dict["hypertext_link"];
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, [hypertext_link]() { wxLaunchDefaultBrowser(hypertext_link); } };
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
// highlight settings
|
||||
} else if (dict["hypertext_type"] == "settings") {
|
||||
std::string opt = dict["hypertext_settings_opt"];
|
||||
Preset::Type type = static_cast<Preset::Type>(std::atoi(dict["hypertext_settings_type"].c_str()));
|
||||
std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]);
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } };
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
// open preferences
|
||||
} else if(dict["hypertext_type"] == "preferences") {
|
||||
int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str()));
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, [page]() { wxGetApp().open_preferences(page); } };
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
|
||||
} else if (dict["hypertext_type"] == "plater") {
|
||||
std::string item = dict["hypertext_plater_item"];
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
} else if (dict["hypertext_type"] == "gizmo") {
|
||||
std::string item = dict["hypertext_gizmo_item"];
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } };
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
}
|
||||
else if (dict["hypertext_type"] == "gallery") {
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, []() { wxGetApp().obj_list()->load_shape_object_from_gallery(); } };
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() {
|
||||
// Deselect all objects, otherwise gallery wont show.
|
||||
wxGetApp().plater()->canvas3D()->deselect_all();
|
||||
wxGetApp().obj_list()->load_shape_object_from_gallery(); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
}
|
||||
} else {
|
||||
// plain text without hypertext
|
||||
HintData hint_data{ text1 };
|
||||
HintData hint_data{ id_string, text1, weight, was_displayed, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
HintData* HintDatabase::get_hint(bool up)
|
||||
HintData* HintDatabase::get_hint(bool new_hint/* = true*/)
|
||||
{
|
||||
if (! m_initialized) {
|
||||
init();
|
||||
//return false;
|
||||
new_hint = true;
|
||||
}
|
||||
// shift id
|
||||
m_hint_id = (up ? m_hint_id + 1 : (m_hint_id == 0 ? m_loaded_hints.size() - 1 : m_hint_id - 1));
|
||||
m_hint_id %= m_loaded_hints.size();
|
||||
if (m_loaded_hints.empty())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "There were no hints loaded from hints.ini file.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (new_hint)
|
||||
m_hint_id = get_next();
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
AppConfig* app_config = wxGetApp().app_config;
|
||||
app_config->set("last_hint", std::to_string(m_hint_id));
|
||||
|
||||
//data = &m_loaded_hints[m_hint_id];
|
||||
/*
|
||||
data.text = m_loaded_hints[m_hint_id].text;
|
||||
data.hypertext = m_loaded_hints[m_hint_id].hypertext;
|
||||
data.follow_text = m_loaded_hints[m_hint_id].follow_text;
|
||||
data.callback = m_loaded_hints[m_hint_id].callback;
|
||||
*/
|
||||
return &m_loaded_hints[m_hint_id];
|
||||
}
|
||||
|
||||
size_t HintDatabase::get_next()
|
||||
{
|
||||
if (!m_sorted_hints)
|
||||
{
|
||||
auto compare_wieght = [](const HintData& a, const HintData& b){ return a.weight < b.weight; };
|
||||
std::sort(m_loaded_hints.begin(), m_loaded_hints.end(), compare_wieght);
|
||||
m_sorted_hints = true;
|
||||
srand(time(NULL));
|
||||
}
|
||||
std::vector<size_t> candidates; // index in m_loaded_hints
|
||||
// total weight
|
||||
size_t total_weight = 0;
|
||||
for (size_t i = 0; i < m_loaded_hints.size(); i++) {
|
||||
if (!m_loaded_hints[i].was_displayed && tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) {
|
||||
candidates.emplace_back(i);
|
||||
total_weight += m_loaded_hints[i].weight;
|
||||
}
|
||||
}
|
||||
// all were shown
|
||||
if (total_weight == 0) {
|
||||
clear_used();
|
||||
for (size_t i = 0; i < m_loaded_hints.size(); i++) {
|
||||
m_loaded_hints[i].was_displayed = false;
|
||||
if (tags_check(m_loaded_hints[i].disabled_tags, m_loaded_hints[i].enabled_tags)) {
|
||||
candidates.emplace_back(i);
|
||||
total_weight += m_loaded_hints[i].weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (total_weight == 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed. No suitable hint was found.";
|
||||
throw std::exception();
|
||||
}
|
||||
size_t random_number = rand() % total_weight + 1;
|
||||
size_t current_weight = 0;
|
||||
for (size_t i = 0; i < candidates.size(); i++) {
|
||||
current_weight += m_loaded_hints[candidates[i]].weight;
|
||||
if (random_number <= current_weight) {
|
||||
set_used(m_loaded_hints[candidates[i]].id_string);
|
||||
m_loaded_hints[candidates[i]].was_displayed = true;
|
||||
return candidates[i];
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint notification random number generator failed.";
|
||||
throw std::exception();
|
||||
}
|
||||
|
||||
bool HintDatabase::is_used(const std::string& id)
|
||||
{
|
||||
// load used ids from file
|
||||
if (!m_used_ids_loaded) {
|
||||
read_used_binary(m_used_ids);
|
||||
m_used_ids_loaded = true;
|
||||
}
|
||||
// check if id is in used
|
||||
for (const std::string& used_id : m_used_ids) {
|
||||
if (used_id == id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void HintDatabase::set_used(const std::string& id)
|
||||
{
|
||||
// check needed?
|
||||
if (!is_used(id))
|
||||
{
|
||||
m_used_ids.emplace_back(id);
|
||||
}
|
||||
}
|
||||
void HintDatabase::clear_used()
|
||||
{
|
||||
m_used_ids.clear();
|
||||
}
|
||||
|
||||
void NotificationManager::HintNotification::count_spaces()
|
||||
{
|
||||
//determine line width
|
||||
|
@ -220,12 +502,16 @@ void NotificationManager::HintNotification::count_spaces()
|
|||
std::string text;
|
||||
text = ImGui::WarningMarker;
|
||||
float picture_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
m_left_indentation = picture_width + m_line_height / 2;
|
||||
m_left_indentation = picture_width * 1.5f + m_line_height / 2;
|
||||
|
||||
// no left button picture
|
||||
//m_left_indentation = m_line_height;
|
||||
|
||||
m_window_width_offset = m_left_indentation + m_line_height * 3.f;// 5.5f; // no right arrow
|
||||
if (m_documentation_link.empty())
|
||||
m_window_width_offset = m_left_indentation + m_line_height * 3.f;
|
||||
else
|
||||
m_window_width_offset = m_left_indentation + m_line_height * 5.5f;
|
||||
|
||||
m_window_width = m_line_height * 25;
|
||||
}
|
||||
|
||||
|
@ -263,7 +549,7 @@ void NotificationManager::HintNotification::count_lines()
|
|||
}
|
||||
// when one word longer than line.
|
||||
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset ||
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 4 * 3
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 5 * 3
|
||||
) {
|
||||
float width_of_a = ImGui::CalcTextSize("a").x;
|
||||
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
|
||||
|
@ -333,7 +619,7 @@ void NotificationManager::HintNotification::count_lines()
|
|||
}
|
||||
// when one word longer than line.
|
||||
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset - size_of_last_line ||
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x + size_of_last_line < (m_window_width - m_window_width_offset) / 4 * 3
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x + size_of_last_line < (m_window_width - m_window_width_offset) / 5 * 3
|
||||
) {
|
||||
float width_of_a = ImGui::CalcTextSize("a").x;
|
||||
int letter_count = (int)((m_window_width - m_window_width_offset - size_of_last_line) / width_of_a);
|
||||
|
@ -387,12 +673,12 @@ void NotificationManager::HintNotification::set_next_window_size(ImGuiWrapper& i
|
|||
m_window_height += 1 * m_line_height; // top and bottom
|
||||
*/
|
||||
|
||||
m_window_height = std::max((m_lines_count + 1.f) * m_line_height, 4.f * m_line_height);
|
||||
m_window_height = std::max((m_lines_count + 1.f) * m_line_height, 5.f * m_line_height);
|
||||
}
|
||||
|
||||
bool NotificationManager::HintNotification::on_text_click()
|
||||
{
|
||||
if (m_hypertext_callback != nullptr && (!m_runtime_disable || disabled_modes_check(m_disabled_modes)))
|
||||
if (m_hypertext_callback != nullptr && (!m_runtime_disable || tags_check(m_disabled_tags, m_enabled_tags)))
|
||||
m_hypertext_callback();
|
||||
return false;
|
||||
}
|
||||
|
@ -405,7 +691,7 @@ void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, con
|
|||
|
||||
float x_offset = m_left_indentation;
|
||||
int last_end = 0;
|
||||
float starting_y = (m_lines_count == 2 ? win_size_y / 2 - m_line_height :(m_lines_count == 1 ? win_size_y / 2 - m_line_height / 2: m_line_height / 2));
|
||||
float starting_y = (m_lines_count < 4 ? m_line_height / 2 * (4 - m_lines_count + 1) : m_line_height / 2);
|
||||
float shift_y = m_line_height;
|
||||
std::string line;
|
||||
|
||||
|
@ -425,8 +711,8 @@ void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, con
|
|||
// regural line
|
||||
line = m_text1.substr(last_end, m_endlines[i] - last_end);
|
||||
}
|
||||
// first line is headline
|
||||
if (i == 0) {
|
||||
// first line is headline (for hint notification it must be divided by \n)
|
||||
if (m_text1.find('\n') >= m_endlines[i]) {
|
||||
line = ImGui::ColorMarkerStart + line + ImGui::ColorMarkerEnd;
|
||||
}
|
||||
// Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line)
|
||||
|
@ -524,16 +810,17 @@ void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& im
|
|||
close();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
|
||||
|
||||
render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
//render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
render_logo(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
render_preferences_button(imgui, win_pos_x, win_pos_y);
|
||||
if (!m_documentation_link.empty() && wxGetApp().app_config->get("suppress_hyperlinks") != "1")
|
||||
{
|
||||
render_documentation_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
|
||||
|
@ -548,12 +835,23 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp
|
|||
std::string button_text;
|
||||
button_text = ImGui::PreferencesButton;
|
||||
//hover
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 15.f, win_pos_y + m_window_height - 1.75f * m_line_height),
|
||||
ImVec2(win_pos_x, win_pos_y + m_window_height),
|
||||
true))
|
||||
{
|
||||
true)) {
|
||||
button_text = ImGui::PreferencesHoverButton;
|
||||
}
|
||||
// tooltip
|
||||
long time_now = wxGetLocalTime();
|
||||
if (m_prefe_hover_time > 0 && m_prefe_hover_time < time_now) {
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
imgui.text(_u8L("Open Preferences."));
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (m_prefe_hover_time == 0)
|
||||
m_prefe_hover_time = time_now;
|
||||
} else
|
||||
m_prefe_hover_time = 0;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
|
@ -568,15 +866,10 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp
|
|||
wxGetApp().open_preferences(2);
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
// preferences button is in place of minimize button
|
||||
m_minimize_b_visible = true;
|
||||
}
|
||||
|
||||
void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
// Used for debuging
|
||||
|
@ -605,11 +898,7 @@ void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapp
|
|||
retrieve_data();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
|
@ -621,38 +910,90 @@ void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, con
|
|||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
std::string button_text;
|
||||
button_text = ImGui::ErrorMarker;//LeftArrowButton;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
std::wstring button_text;
|
||||
button_text = ImGui::ClippyMarker;//LeftArrowButton;
|
||||
std::string placeholder_text;
|
||||
placeholder_text = ImGui::EjectButton;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f * 2.f, button_pic_size.y * 1.25f * 2.f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y * 1.1f);
|
||||
ImGui::SetCursorPosX(0);
|
||||
// shouldnt it render as text?
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
void NotificationManager::HintNotification::retrieve_data(size_t recursion_counter)
|
||||
void NotificationManager::HintNotification::render_documentation_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
HintData* hint_data = HintDatabase::get_instance().get_hint(true);
|
||||
if (hint_data != nullptr && !disabled_modes_check(hint_data->disabled_modes))
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
std::wstring button_text;
|
||||
button_text = ImGui::DocumentationButton;
|
||||
std::string placeholder_text;
|
||||
placeholder_text = ImGui::EjectButton;
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
|
||||
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y - 2 * m_line_height),
|
||||
true))
|
||||
{
|
||||
// Content for different user - retrieve another
|
||||
size_t count = HintDatabase::get_instance().get_count();
|
||||
if (count < recursion_counter) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint notification failed to load data due to recursion counter.";
|
||||
} else {
|
||||
retrieve_data(recursion_counter + 1);
|
||||
button_text = ImGui::DocumentationHoverButton;
|
||||
// tooltip
|
||||
long time_now = wxGetLocalTime();
|
||||
if (m_docu_hover_time > 0 && m_docu_hover_time < time_now) {
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
imgui.text(_u8L("Open Documentation in web browser."));
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
return;
|
||||
if (m_docu_hover_time == 0)
|
||||
m_docu_hover_time = time_now;
|
||||
}
|
||||
else
|
||||
m_docu_hover_time = 0;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
open_documentation();
|
||||
}
|
||||
|
||||
//invisible large button
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
|
||||
ImGui::SetCursorPosY(0);
|
||||
if (imgui.button(" ", m_line_height * 2.f, win_size.y - 2 * m_line_height))
|
||||
{
|
||||
open_documentation();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
|
||||
void NotificationManager::HintNotification::open_documentation()
|
||||
{
|
||||
if (!m_documentation_link.empty())
|
||||
{
|
||||
launch_browser_if_allowed(m_documentation_link);
|
||||
}
|
||||
}
|
||||
void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true*/)
|
||||
{
|
||||
HintData* hint_data = HintDatabase::get_instance().get_hint(new_hint);
|
||||
if (hint_data == nullptr)
|
||||
close();
|
||||
|
||||
if(hint_data != nullptr)
|
||||
{
|
||||
NotificationData nd { NotificationType::DidYouKnowHint,
|
||||
|
@ -661,12 +1002,13 @@ void NotificationManager::HintNotification::retrieve_data(size_t recursion_count
|
|||
hint_data->text,
|
||||
hint_data->hypertext, nullptr,
|
||||
hint_data->follow_text };
|
||||
update(nd);
|
||||
m_hypertext_callback = hint_data->callback;
|
||||
m_disabled_modes = hint_data->disabled_modes;
|
||||
m_runtime_disable = hint_data->runtime_disable;
|
||||
m_has_hint_data = true;
|
||||
|
||||
m_disabled_tags = hint_data->disabled_tags;
|
||||
m_enabled_tags = hint_data->enabled_tags;
|
||||
m_runtime_disable = hint_data->runtime_disable;
|
||||
m_documentation_link = hint_data->documentation_link;
|
||||
m_has_hint_data = true;
|
||||
update(nd);
|
||||
}
|
||||
}
|
||||
} //namespace Slic3r
|
||||
|
|
|
@ -9,12 +9,17 @@ namespace GUI {
|
|||
// Database of hints updatable
|
||||
struct HintData
|
||||
{
|
||||
std::string id_string;
|
||||
std::string text;
|
||||
size_t weight;
|
||||
bool was_displayed;
|
||||
std::string hypertext;
|
||||
std::string follow_text;
|
||||
std::string disabled_modes;
|
||||
std::string disabled_tags;
|
||||
std::string enabled_tags;
|
||||
bool runtime_disable; // if true - hyperlink will check before every click if not in disabled mode
|
||||
std::function<void(void)> callback{ nullptr };
|
||||
std::string documentation_link;
|
||||
std::function<void(void)> callback { nullptr };
|
||||
};
|
||||
|
||||
class HintDatabase
|
||||
|
@ -31,11 +36,12 @@ private:
|
|||
: m_hint_id(0)
|
||||
{}
|
||||
public:
|
||||
~HintDatabase();
|
||||
HintDatabase(HintDatabase const&) = delete;
|
||||
void operator=(HintDatabase const&) = delete;
|
||||
|
||||
// return true if HintData filled;
|
||||
HintData* get_hint(bool up = true);
|
||||
HintData* get_hint(bool new_hint = true);
|
||||
size_t get_count() {
|
||||
if (!m_initialized)
|
||||
return 0;
|
||||
|
@ -44,21 +50,29 @@ public:
|
|||
private:
|
||||
void init();
|
||||
void load_hints_from_file(const boost::filesystem::path& path);
|
||||
bool is_used(const std::string& id);
|
||||
void set_used(const std::string& id);
|
||||
void clear_used();
|
||||
// Returns position in m_loaded_hints with next hint chosed randomly with weights
|
||||
size_t get_next();
|
||||
size_t m_hint_id;
|
||||
bool m_initialized { false };
|
||||
std::vector<HintData> m_loaded_hints;
|
||||
|
||||
bool m_sorted_hints { false };
|
||||
std::vector<std::string> m_used_ids;
|
||||
bool m_used_ids_loaded { false };
|
||||
};
|
||||
// Notification class - shows current Hint ("Did you know")
|
||||
class NotificationManager::HintNotification : public NotificationManager::PopNotification
|
||||
{
|
||||
public:
|
||||
HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler)
|
||||
HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint)
|
||||
: PopNotification(n, id_provider, evt_handler)
|
||||
{
|
||||
retrieve_data();
|
||||
retrieve_data(new_hint);
|
||||
}
|
||||
virtual void init() override;
|
||||
void open_next() { retrieve_data(); }
|
||||
protected:
|
||||
virtual void set_next_window_size(ImGuiWrapper& imgui) override;
|
||||
virtual void count_spaces() override;
|
||||
|
@ -77,18 +91,27 @@ protected:
|
|||
void render_right_arrow_button(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_documentation_button(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_logo(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
|
||||
void retrieve_data(size_t recursion_counter = 0);
|
||||
// recursion counter -1 tells to retrieve same hint as last time
|
||||
void retrieve_data(bool new_hint = true);
|
||||
void open_documentation();
|
||||
|
||||
bool m_has_hint_data { false };
|
||||
std::function<void(void)> m_hypertext_callback;
|
||||
std::string m_disabled_modes;
|
||||
std::string m_disabled_tags;
|
||||
std::string m_enabled_tags;
|
||||
bool m_runtime_disable;
|
||||
std::string m_documentation_link;
|
||||
float m_close_b_y { 0 };
|
||||
float m_close_b_w { 0 };
|
||||
// hover of buttons
|
||||
long m_docu_hover_time { 0 };
|
||||
long m_prefe_hover_time{ 0 };
|
||||
};
|
||||
|
||||
} //namespace Slic3r
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace Slic3r {
|
|||
namespace GUI {
|
||||
|
||||
|
||||
static const std::map<const char, std::string> font_icons = {
|
||||
static const std::map<const wchar_t, std::string> font_icons = {
|
||||
{ImGui::PrintIconMarker , "cog" },
|
||||
{ImGui::PrinterIconMarker , "printer" },
|
||||
{ImGui::PrinterSlaIconMarker , "sla_printer" },
|
||||
|
@ -49,21 +49,27 @@ static const std::map<const char, std::string> font_icons = {
|
|||
{ImGui::PreferencesButton , "notification_preferences" },
|
||||
{ImGui::PreferencesHoverButton , "notification_preferences_hover"},
|
||||
};
|
||||
static const std::map<const char, std::string> font_icons_large = {
|
||||
{ImGui::CloseNotifButton , "notification_close" },
|
||||
{ImGui::CloseNotifHoverButton , "notification_close_hover" },
|
||||
{ImGui::EjectButton , "notification_eject_sd" },
|
||||
{ImGui::EjectHoverButton , "notification_eject_sd_hover" },
|
||||
{ImGui::WarningMarker , "notification_warning" },
|
||||
{ImGui::ErrorMarker , "notification_error" },
|
||||
{ImGui::CancelButton , "notification_cancel" },
|
||||
{ImGui::CancelHoverButton , "notification_cancel_hover" },
|
||||
{ImGui::SinkingObjectMarker , "move" },
|
||||
{ImGui::CustomSupportsMarker , "fdm_supports" },
|
||||
{ImGui::CustomSeamMarker , "seam" },
|
||||
{ImGui::MmuSegmentationMarker , "move" },
|
||||
{ImGui::VarLayerHeightMarker , "layers" },
|
||||
|
||||
static const std::map<const wchar_t, std::string> font_icons_large = {
|
||||
{ImGui::CloseNotifButton , "notification_close" },
|
||||
{ImGui::CloseNotifHoverButton , "notification_close_hover" },
|
||||
{ImGui::EjectButton , "notification_eject_sd" },
|
||||
{ImGui::EjectHoverButton , "notification_eject_sd_hover" },
|
||||
{ImGui::WarningMarker , "notification_warning" },
|
||||
{ImGui::ErrorMarker , "notification_error" },
|
||||
{ImGui::CancelButton , "notification_cancel" },
|
||||
{ImGui::CancelHoverButton , "notification_cancel_hover" },
|
||||
{ImGui::SinkingObjectMarker , "move" },
|
||||
{ImGui::CustomSupportsMarker , "fdm_supports" },
|
||||
{ImGui::CustomSeamMarker , "seam" },
|
||||
{ImGui::MmuSegmentationMarker , "mmu_segmentation" },
|
||||
{ImGui::VarLayerHeightMarker , "layers" },
|
||||
{ImGui::DocumentationButton , "notification_documentation" },
|
||||
{ImGui::DocumentationHoverButton, "notification_documentation_hover"},
|
||||
};
|
||||
|
||||
static const std::map<const wchar_t, std::string> font_icons_extra_large = {
|
||||
{ImGui::ClippyMarker , "notification_clippy" },
|
||||
|
||||
};
|
||||
|
||||
const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f };
|
||||
|
@ -989,6 +995,8 @@ void ImGuiWrapper::init_font(bool compress)
|
|||
io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz);
|
||||
for (auto& icon : font_icons_large)
|
||||
io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2);
|
||||
for (auto& icon : font_icons_extra_large)
|
||||
io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4);
|
||||
|
||||
// Build texture atlas
|
||||
unsigned char* pixels;
|
||||
|
@ -1027,6 +1035,22 @@ void ImGuiWrapper::init_font(bool compress)
|
|||
rect_id++;
|
||||
}
|
||||
|
||||
icon_sz *= 2; // default size of extra large icon is 64 px
|
||||
for (auto icon : font_icons_extra_large) {
|
||||
if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) {
|
||||
assert(rect->Width == icon_sz);
|
||||
assert(rect->Height == icon_sz);
|
||||
std::vector<unsigned char> raw_data = load_svg(icon.second, icon_sz, icon_sz);
|
||||
const ImU32* pIn = (ImU32*)raw_data.data();
|
||||
for (int y = 0; y < icon_sz; y++) {
|
||||
ImU32* pOut = (ImU32*)pixels + (rect->Y + y) * width + (rect->X);
|
||||
for (int x = 0; x < icon_sz; x++)
|
||||
*pOut++ = *pIn++;
|
||||
}
|
||||
}
|
||||
rect_id++;
|
||||
}
|
||||
|
||||
// Upload texture to graphics system
|
||||
GLint last_texture;
|
||||
glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
|
||||
|
|
|
@ -35,7 +35,9 @@ protected:
|
|||
void prepare() override;
|
||||
|
||||
void on_exception(const std::exception_ptr &) override;
|
||||
|
||||
|
||||
void process() override;
|
||||
|
||||
public:
|
||||
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
|
@ -46,8 +48,6 @@ public:
|
|||
return int(m_selected.size() + m_unprintable.size());
|
||||
}
|
||||
|
||||
void process() override;
|
||||
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
|
|
|
@ -147,26 +147,28 @@ void FillBedJob::finalize()
|
|||
|
||||
size_t inst_cnt = model_object->instances.size();
|
||||
|
||||
for (ArrangePolygon &ap : m_selected) {
|
||||
if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0))
|
||||
ap.apply();
|
||||
}
|
||||
int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0, [](int s, auto &ap) {
|
||||
return s + int(ap.priority == 0 && ap.bed_idx == 0);
|
||||
});
|
||||
|
||||
model_object->ensure_on_bed();
|
||||
if (added_cnt > 0) {
|
||||
for (ArrangePolygon &ap : m_selected) {
|
||||
if (ap.bed_idx != arrangement::UNARRANGED && (ap.priority != 0 || ap.bed_idx == 0))
|
||||
ap.apply();
|
||||
}
|
||||
|
||||
m_plater->update();
|
||||
model_object->ensure_on_bed();
|
||||
|
||||
int added_cnt = std::accumulate(m_selected.begin(), m_selected.end(), 0,
|
||||
[](int s, auto &ap) {
|
||||
return s + int(ap.priority == 0 && ap.bed_idx == 0);
|
||||
});
|
||||
m_plater->update();
|
||||
|
||||
// FIXME: somebody explain why this is needed for increase_object_instances
|
||||
if (inst_cnt == 1) added_cnt++;
|
||||
// FIXME: somebody explain why this is needed for increase_object_instances
|
||||
if (inst_cnt == 1) added_cnt++;
|
||||
|
||||
if (added_cnt > 0)
|
||||
m_plater->sidebar()
|
||||
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));
|
||||
}
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
|
|
@ -24,6 +24,7 @@ class FillBedJob : public PlaterJob
|
|||
protected:
|
||||
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
|
||||
public:
|
||||
FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
|
@ -35,8 +36,6 @@ public:
|
|||
return m_status_range;
|
||||
}
|
||||
|
||||
void process() override;
|
||||
|
||||
void finalize() override;
|
||||
};
|
||||
|
||||
|
|
|
@ -49,11 +49,20 @@ protected:
|
|||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
|
||||
// The method where the actual work of the job should be defined.
|
||||
virtual void process() = 0;
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
virtual void finalize() { m_finalized = true; }
|
||||
|
||||
virtual void on_exception(const std::exception_ptr &) {}
|
||||
// Exceptions occuring in process() are redirected from the worker thread
|
||||
// into the main (UI) thread. This method is called from the main thread and
|
||||
// can be overriden to handle these exceptions.
|
||||
virtual void on_exception(const std::exception_ptr &eptr)
|
||||
{
|
||||
if (eptr) std::rethrow_exception(eptr);
|
||||
}
|
||||
|
||||
public:
|
||||
Job(std::shared_ptr<ProgressIndicator> pri);
|
||||
|
@ -65,8 +74,6 @@ public:
|
|||
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
|
||||
|
|
|
@ -15,14 +15,21 @@ class RotoptimizeJob : public PlaterJob
|
|||
using FindFn = std::function<Vec2d(const ModelObject & mo,
|
||||
const sla::RotOptimizeParams ¶ms)>;
|
||||
|
||||
struct FindMethod { std::string name; FindFn findfn; };
|
||||
struct FindMethod { std::string name; FindFn findfn; std::string descr; };
|
||||
|
||||
static inline const FindMethod Methods[] = {
|
||||
{ L("Best surface quality"), sla::find_best_misalignment_rotation },
|
||||
{ L("Least supports"), sla::find_least_supports_rotation },
|
||||
// Just a min area bounding box that is done for all methods anyway.
|
||||
{ L("Z axis only"), nullptr }
|
||||
};
|
||||
static inline const FindMethod Methods[]
|
||||
= {{L("Best surface quality"),
|
||||
sla::find_best_misalignment_rotation,
|
||||
L("Optimize object rotation for best surface quality.")},
|
||||
{L("Reduced overhang slopes"),
|
||||
sla::find_least_supports_rotation,
|
||||
L("Optimize object rotation to have minimum amount of overhangs needing support "
|
||||
"structures.\nNote that this method will try to find the best surface of the object "
|
||||
"for touching the print bed if no elevation is set.")},
|
||||
// Just a min area bounding box that is done for all methods anyway.
|
||||
{L("Lowest Z height"),
|
||||
sla::find_min_z_height_rotation,
|
||||
L("Rotate the model to have the lowest z height for faster print time.")}};
|
||||
|
||||
size_t m_method_id = 0;
|
||||
float m_accuracy = 0.75;
|
||||
|
@ -41,31 +48,26 @@ class RotoptimizeJob : public PlaterJob
|
|||
protected:
|
||||
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
|
||||
public:
|
||||
|
||||
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
|
||||
: PlaterJob{std::move(pri), plater}
|
||||
{}
|
||||
|
||||
void process() override;
|
||||
|
||||
void finalize() override;
|
||||
|
||||
static constexpr size_t get_methods_count() { return std::size(Methods); }
|
||||
static const auto & get_method_names()
|
||||
|
||||
static std::string get_method_name(size_t i)
|
||||
{
|
||||
static bool m_method_names_valid = false;
|
||||
static std::array<std::string, std::size(Methods)> m_method_names;
|
||||
return _utf8(Methods[i].name);
|
||||
}
|
||||
|
||||
if (!m_method_names_valid) {
|
||||
|
||||
for (size_t i = 0; i < std::size(Methods); ++i)
|
||||
m_method_names[i] = _utf8(Methods[i].name);
|
||||
|
||||
m_method_names_valid = true;
|
||||
}
|
||||
|
||||
return m_method_names;
|
||||
static std::string get_method_description(size_t i)
|
||||
{
|
||||
return _utf8(Methods[i].descr);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
|
||||
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",
|
||||
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.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);
|
||||
|
@ -119,6 +119,7 @@ public:
|
|||
wxString path;
|
||||
Vec2i win = {2, 2};
|
||||
std::string err;
|
||||
ConfigSubstitutions config_substitutions;
|
||||
|
||||
priv(Plater *plt) : plater{plt} {}
|
||||
};
|
||||
|
@ -140,27 +141,22 @@ void SLAImportJob::process()
|
|||
if (p->path.empty()) return;
|
||||
|
||||
std::string path = p->path.ToUTF8().data();
|
||||
ConfigSubstitutions config_substitutions;
|
||||
try {
|
||||
switch (p->sel) {
|
||||
case Sel::modelAndProfile:
|
||||
config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
break;
|
||||
case Sel::modelOnly:
|
||||
config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
|
||||
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
|
||||
break;
|
||||
case Sel::profileOnly:
|
||||
config_substitutions = import_sla_archive(path, p->profile);
|
||||
p->config_substitutions = import_sla_archive(path, p->profile);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
p->err = ex.what();
|
||||
}
|
||||
|
||||
if (! config_substitutions.empty()) {
|
||||
//FIXME Add reporting here "Loading profiles found following incompatibilities."
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
|
||||
_(L("Importing done.")));
|
||||
|
@ -187,6 +183,7 @@ void SLAImportJob::prepare()
|
|||
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
|
||||
p->sel = dlg.get_selection();
|
||||
p->win = dlg.get_marchsq_windowsize();
|
||||
p->config_substitutions.clear();
|
||||
} else {
|
||||
p->path = "";
|
||||
}
|
||||
|
@ -230,8 +227,11 @@ void SLAImportJob::finalize()
|
|||
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{p->mesh},
|
||||
name, is_centered);
|
||||
}
|
||||
|
||||
|
||||
if (! p->config_substitutions.empty())
|
||||
show_substitutions_info(p->config_substitutions, p->path.ToUTF8().data());
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
}}
|
||||
}} // namespace Slic3r::GUI
|
||||
|
|
|
@ -10,18 +10,16 @@ class SLAImportJob : public PlaterJob {
|
|||
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
void finalize() override;
|
||||
|
||||
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
|
||||
|
|
|
@ -151,6 +151,9 @@ void KBShortcutsDialog::fill_shortcuts()
|
|||
{ "F", L("Gizmo Place face on bed") },
|
||||
{ "H", L("Gizmo SLA hollow") },
|
||||
{ "L", L("Gizmo SLA support points") },
|
||||
{ "L", L("Gizmo FDM paint-on supports") },
|
||||
{ "P", L("Gizmo FDM paint-on seam") },
|
||||
{ "N", L("Gizmo Multi Material painting") },
|
||||
{ "Esc", L("Unselect gizmo or clear selection") },
|
||||
{ "K", L("Change camera type (perspective, orthographic)") },
|
||||
{ "B", L("Zoom to Bed") },
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "GUI_Factories.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GalleryDialog.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <dbt.h>
|
||||
|
@ -219,15 +220,19 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_plater != nullptr && !m_plater->save_project_if_dirty()) {
|
||||
event.Veto();
|
||||
return;
|
||||
if (m_plater != nullptr) {
|
||||
int saved_project = m_plater->save_project_if_dirty();
|
||||
if (saved_project == wxID_CANCEL) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
// check unsaved changes only if project wasn't saved
|
||||
else if (saved_project == wxID_NO && event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
if (event.CanVeto() && !wxGetApp().check_print_host_queue()) {
|
||||
event.Veto();
|
||||
return;
|
||||
|
@ -613,8 +618,11 @@ void MainFrame::update_title()
|
|||
// Don't try to remove the extension, it would remove part of the file name after the last dot!
|
||||
wxString project = from_path(into_path(m_plater->get_project_filename()).filename());
|
||||
wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : "";
|
||||
if (!dirty_marker.empty() || !project.empty())
|
||||
if (!dirty_marker.empty() || !project.empty()) {
|
||||
if (!dirty_marker.empty() && project.empty())
|
||||
project = _("Untitled");
|
||||
title = dirty_marker + project + " - ";
|
||||
}
|
||||
}
|
||||
|
||||
std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID;
|
||||
|
@ -1049,7 +1057,7 @@ static wxMenu* generate_help_menu()
|
|||
append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"),
|
||||
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"),
|
||||
[](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); });
|
||||
[](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); });
|
||||
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
|
||||
//# wxTheApp->check_version(1);
|
||||
//# });
|
||||
|
@ -1059,20 +1067,22 @@ static wxMenu* generate_help_menu()
|
|||
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); });
|
||||
// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Manual"), SLIC3R_APP_NAME),
|
||||
// wxString::Format(_L("Open the %s manual in your browser"), SLIC3R_APP_NAME),
|
||||
// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); });
|
||||
// [this](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("http://manual.slic3r.org/"); });
|
||||
helpMenu->AppendSeparator();
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
|
||||
[](wxCommandEvent&) { wxGetApp().system_info(); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
|
||||
[](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME),
|
||||
[](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); });
|
||||
[](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/slic3r/issues/new"); });
|
||||
if (wxGetApp().is_editor())
|
||||
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"),
|
||||
[](wxCommandEvent&) { Slic3r::GUI::about(); });
|
||||
else
|
||||
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), GCODEVIEWER_APP_NAME), _L("Show about dialog"),
|
||||
[](wxCommandEvent&) { Slic3r::GUI::about(); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Show Tip of the day"), _L("Opens Tip of the day notification in bottom right corner or shows another tip if already opened."),
|
||||
[](wxCommandEvent&) { wxGetApp().plater()->get_notification_manager()->push_hint_notification(false); });
|
||||
helpMenu->AppendSeparator();
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"),
|
||||
[](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
|
||||
|
@ -1181,7 +1191,7 @@ void MainFrame::init_menubar_as_editor()
|
|||
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "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 archive"),
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S archive") + dots, _L("Load an SL1 / Sl1S archive"),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
|
||||
[this](){return m_plater != nullptr; }, this);
|
||||
|
||||
|
@ -1742,12 +1752,8 @@ bool MainFrame::load_config_file(const std::string &path)
|
|||
{
|
||||
try {
|
||||
ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (!config_substitutions.empty())
|
||||
show_substitutions_info(config_substitutions, path);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return false;
|
||||
|
@ -1806,18 +1812,16 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
|
|||
size_t presets_imported = 0;
|
||||
PresetsConfigSubstitutions config_substitutions;
|
||||
try {
|
||||
std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported);
|
||||
// Report all substitutions.
|
||||
std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(
|
||||
file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (! config_substitutions.empty())
|
||||
show_substitutions_info(config_substitutions);
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
wxGetApp().load_current_presets();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/TriangleMeshSlicer.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
|
||||
|
@ -24,6 +25,15 @@ void MeshClipper::set_plane(const ClippingPlane& plane)
|
|||
}
|
||||
|
||||
|
||||
void MeshClipper::set_limiting_plane(const ClippingPlane& plane)
|
||||
{
|
||||
if (m_limiting_plane != plane) {
|
||||
m_limiting_plane = plane;
|
||||
m_triangles_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MeshClipper::set_mesh(const TriangleMesh& mesh)
|
||||
{
|
||||
|
@ -78,29 +88,82 @@ void MeshClipper::recalculate_triangles()
|
|||
float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
|
||||
|
||||
// Now do the cutting
|
||||
MeshSlicingParamsEx slicing_params;
|
||||
MeshSlicingParams slicing_params;
|
||||
slicing_params.trafo.rotate(Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(up, Vec3d::UnitZ()));
|
||||
|
||||
assert(m_mesh->has_shared_vertices());
|
||||
std::vector<ExPolygons> list_of_expolys = slice_mesh_ex(m_mesh->its, std::vector<float>{height_mesh}, slicing_params);
|
||||
ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
|
||||
|
||||
if (m_negative_mesh && !m_negative_mesh->empty()) {
|
||||
assert(m_negative_mesh->has_shared_vertices());
|
||||
std::vector<ExPolygons> neg_polys = slice_mesh_ex(m_negative_mesh->its, std::vector<float>{height_mesh}, slicing_params);
|
||||
list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front());
|
||||
ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
|
||||
expolys = diff_ex(expolys, neg_expolys);
|
||||
}
|
||||
|
||||
m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.);
|
||||
|
||||
// Rotate the cut into world coords:
|
||||
// Triangulate and rotate the cut into world coords:
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d::UnitZ(), up);
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.rotate(q);
|
||||
tr = m_trafo.get_matrix() * tr;
|
||||
height_mesh += 0.001f; // to avoid z-fighting
|
||||
|
||||
// to avoid z-fighting
|
||||
height_mesh += 0.001f;
|
||||
if (m_limiting_plane != ClippingPlane::ClipsNothing())
|
||||
{
|
||||
// Now remove whatever ended up below the limiting plane (e.g. sinking objects).
|
||||
// First transform the limiting plane from world to mesh coords.
|
||||
// Note that inverse of tr transforms the plane from world to horizontal.
|
||||
Vec3d normal_old = m_limiting_plane.get_normal().normalized();
|
||||
Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
|
||||
|
||||
// normal_new should now be the plane normal in mesh coords. To find the offset,
|
||||
// transform a point and set offset so it belongs to the transformed plane.
|
||||
Vec3d pt = Vec3d::Zero();
|
||||
double plane_offset = m_limiting_plane.get_data()[3];
|
||||
if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57
|
||||
pt.z() = - plane_offset / normal_old.z();
|
||||
else if (std::abs(normal_old.y()) > 0.5)
|
||||
pt.y() = - plane_offset / normal_old.y();
|
||||
else
|
||||
pt.x() = - plane_offset / normal_old.x();
|
||||
pt = tr.inverse() * pt;
|
||||
double offset = -(normal_new.dot(pt));
|
||||
|
||||
if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) {
|
||||
// The cuts are parallel, show all or nothing.
|
||||
if (offset < height_mesh)
|
||||
expolys.clear();
|
||||
} else {
|
||||
// The cut is a horizontal plane defined by z=height_mesh.
|
||||
// ax+by+e=0 is the line of intersection with the limiting plane.
|
||||
// Normalized so a^2 + b^2 = 1.
|
||||
double len = std::hypot(normal_new.x(), normal_new.y());
|
||||
if (len == 0.)
|
||||
return;
|
||||
double a = normal_new.x() / len;
|
||||
double b = normal_new.y() / len;
|
||||
double e = (normal_new.z() * height_mesh + offset) / len;
|
||||
if (b == 0.)
|
||||
return;
|
||||
|
||||
// We need a half-plane to limit the cut. Get angle of the intersecting line.
|
||||
double angle = std::atan(-a/b);
|
||||
if (b > 0) // select correct half-plane
|
||||
angle += M_PI;
|
||||
|
||||
// We'll take a big rectangle above x-axis and rotate and translate
|
||||
// it so it lies on our line. This will be the figure to subtract
|
||||
// from the cut. The coordinates must not overflow after the transform,
|
||||
// make the rectangle a bit smaller.
|
||||
coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
|
||||
Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
|
||||
ep.front().rotate(angle);
|
||||
ep.front().translate(scale_(-e * a), scale_(-e * b));
|
||||
expolys = diff_ex(expolys, ep);
|
||||
}
|
||||
}
|
||||
|
||||
m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
|
||||
|
||||
m_vertex_array.release_geometry();
|
||||
for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
|
||||
|
@ -159,17 +222,19 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
|||
|
||||
unsigned i = 0;
|
||||
|
||||
// Remove points that are obscured or cut by the clipping plane
|
||||
if (clipping_plane) {
|
||||
for (i=0; i<hits.size(); ++i)
|
||||
if (! clipping_plane->is_point_clipped(trafo * hits[i].position()))
|
||||
break;
|
||||
// Remove points that are obscured or cut by the clipping plane.
|
||||
// Also, remove anything below the bed (sinking objects).
|
||||
for (i=0; i<hits.size(); ++i) {
|
||||
Vec3d transformed_hit = trafo * hits[i].position();
|
||||
if (transformed_hit.z() >= SINKING_Z_THRESHOLD &&
|
||||
(! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit)))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
||||
// All hits are either clipped, or there is an odd number of unclipped
|
||||
// hits - meaning the nearest must be from inside the mesh.
|
||||
return false;
|
||||
}
|
||||
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
||||
// All hits are either clipped, or there is an odd number of unclipped
|
||||
// hits - meaning the nearest must be from inside the mesh.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now stuff the points in the provided vector and calculate normals if asked about them:
|
||||
|
|
|
@ -71,6 +71,11 @@ public:
|
|||
// This is supposed to be in world coordinates.
|
||||
void set_plane(const ClippingPlane& plane);
|
||||
|
||||
// In case the object is clipped by two planes (e.g. in case of sinking
|
||||
// objects), this will be used to clip the triagnulated cut.
|
||||
// Pass ClippingPlane::ClipsNothing to turn this off.
|
||||
void set_limiting_plane(const ClippingPlane& plane);
|
||||
|
||||
// Which mesh to cut. MeshClipper remembers const * to it, caller
|
||||
// must make sure that it stays valid.
|
||||
void set_mesh(const TriangleMesh& mesh);
|
||||
|
@ -92,6 +97,7 @@ private:
|
|||
const TriangleMesh* m_mesh = nullptr;
|
||||
const TriangleMesh* m_negative_mesh = nullptr;
|
||||
ClippingPlane m_plane;
|
||||
ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing();
|
||||
std::vector<Vec2f> m_triangles2d;
|
||||
GLIndexedVertexArray m_vertex_array;
|
||||
bool m_triangles_valid = false;
|
||||
|
|
|
@ -61,7 +61,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
|
|||
logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
|
||||
|
||||
topsizer->Add(logo, 0, wxALL, BORDER);
|
||||
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
|
||||
topsizer->Add(rightsizer, 1, wxTOP | wxBOTTOM | wxRIGHT | wxEXPAND, BORDER);
|
||||
|
||||
SetSizerAndFit(topsizer);
|
||||
}
|
||||
|
@ -98,7 +98,6 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
|||
msg_lines++;
|
||||
}
|
||||
|
||||
html->SetMinSize(wxSize(40 * wxGetApp().em_unit(), monospaced_font ? 30 * wxGetApp().em_unit() : 2 * msg_lines * wxGetApp().em_unit()));
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
wxFont monospace = wxGetApp().code_font();
|
||||
wxColour text_clr = wxGetApp().get_label_clr_default();
|
||||
|
@ -109,6 +108,30 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
|||
int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
|
||||
html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
|
||||
// calculate html page size from text
|
||||
wxSize page_size;
|
||||
int em = wxGetApp().em_unit();
|
||||
|
||||
// if message containes the table
|
||||
if (msg.Contains("<tr>")) {
|
||||
int lines = msg.Freq('\n') + 1;
|
||||
int pos = 0;
|
||||
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
|
||||
pos = msg.find("<tr>", pos + 1);
|
||||
lines += 2;
|
||||
}
|
||||
int page_height = std::min(int(font.GetPixelSize().y+2) * lines, 68 * em);
|
||||
page_size = wxSize(68 * em, page_height);
|
||||
}
|
||||
else {
|
||||
wxClientDC dc(parent);
|
||||
wxSize msg_sz = dc.GetMultiLineTextExtent(msg);
|
||||
page_size = wxSize(std::min(msg_sz.GetX() + 2 * em, 68 * em),
|
||||
std::min(msg_sz.GetY() + 2 * em, 68 * em));
|
||||
}
|
||||
html->SetMinSize(page_size);
|
||||
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data());
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
|
@ -116,7 +139,7 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
|||
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
|
||||
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
|
||||
content_sizer->Add(html, 1, wxEXPAND | wxBOTTOM, 30);
|
||||
content_sizer->Add(html, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
// ErrorDialog
|
||||
|
@ -131,7 +154,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
|
|||
add_btn(wxID_OK, true);
|
||||
|
||||
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/92));
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
|
@ -155,7 +178,7 @@ WarningDialog::WarningDialog(wxWindow *parent,
|
|||
if (style & wxYES) add_btn(wxID_YES);
|
||||
if (style & wxNO) add_btn(wxID_NO);
|
||||
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 90));
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
|
@ -179,8 +202,8 @@ MessageDialog::MessageDialog(wxWindow* parent,
|
|||
if (style & wxCANCEL) add_btn(wxID_CANCEL);
|
||||
|
||||
logo->SetBitmap(create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
|
||||
style & wxICON_INFORMATION ? "info.png" :
|
||||
style & wxICON_QUESTION ? "question" : "PrusaSlicer_192px_grayscale.png", this, 90));
|
||||
style & wxICON_INFORMATION ? "info" :
|
||||
style & wxICON_QUESTION ? "question" : "PrusaSlicer_192px_grayscale.png", this, 84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
|
@ -188,5 +211,57 @@ MessageDialog::MessageDialog(wxWindow* parent,
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
// InfoDialog
|
||||
|
||||
InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg)
|
||||
: MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title)
|
||||
, msg(msg)
|
||||
{
|
||||
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
|
||||
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
|
||||
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||
{
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
wxFont monospace = wxGetApp().code_font();
|
||||
wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
|
||||
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
|
||||
const int font_size = font.GetPointSize() - 1;
|
||||
int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
|
||||
html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
|
||||
// calculate html page size from text
|
||||
int lines = msg.Freq('\n');
|
||||
|
||||
if (msg.Contains("<tr>")) {
|
||||
int pos = 0;
|
||||
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
|
||||
pos = msg.find("<tr>", pos + 1);
|
||||
lines+=2;
|
||||
}
|
||||
}
|
||||
int page_height = std::min((font.GetPixelSize().y + 1) * lines, 68 * wxGetApp().em_unit());
|
||||
wxSize page_size(68 * wxGetApp().em_unit(), page_height);
|
||||
|
||||
html->SetMinSize(page_size);
|
||||
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), true);
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
|
||||
content_sizer->Add(html, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
// Set info bitmap
|
||||
logo->SetBitmap(create_scaled_bitmap("info", this, 84));
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,22 @@ public:
|
|||
#endif
|
||||
|
||||
|
||||
// Generic info dialog, used for displaying exceptions
|
||||
class InfoDialog : public MsgDialog
|
||||
{
|
||||
public:
|
||||
InfoDialog(wxWindow *parent, const wxString &title, const wxString &msg);
|
||||
InfoDialog(InfoDialog&&) = delete;
|
||||
InfoDialog(const InfoDialog&) = delete;
|
||||
InfoDialog&operator=(InfoDialog&&) = delete;
|
||||
InfoDialog&operator=(const InfoDialog&) = delete;
|
||||
virtual ~InfoDialog() = default;
|
||||
|
||||
private:
|
||||
wxString msg;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent, bool add_mode_buttons/* = fal
|
|||
m_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
this->SetSizer(m_sizer);
|
||||
|
||||
m_buttons_sizer = new wxFlexGridSizer(4, m_btn_margin, m_btn_margin);
|
||||
m_buttons_sizer = new wxFlexGridSizer(1, m_btn_margin, m_btn_margin);
|
||||
m_sizer->Add(m_buttons_sizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxBOTTOM, m_btn_margin);
|
||||
|
||||
if (add_mode_buttons) {
|
||||
|
@ -111,6 +111,7 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString& text, bool bSelect/*
|
|||
Slic3r::GUI::wxGetApp().UpdateDarkUI(btn);
|
||||
m_pageButtons.insert(m_pageButtons.begin() + n, btn);
|
||||
m_buttons_sizer->Insert(n, new wxSizerItem(btn));
|
||||
m_buttons_sizer->SetCols(m_buttons_sizer->GetCols() + 1);
|
||||
m_sizer->Layout();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -42,12 +42,12 @@ const NotificationManager::NotificationData NotificationManager::basic_notificat
|
|||
}
|
||||
},
|
||||
{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
|
||||
wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
|
||||
wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
|
||||
{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10,
|
||||
_u8L("You have just added a G-code for color change, but its value is empty.\n"
|
||||
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
|
||||
{NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10,
|
||||
_u8L("This model doesn't allow to automatically add the color changes") },
|
||||
_u8L("No color change event was added to the print. The print does not look like a sign.") },
|
||||
{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10,
|
||||
_u8L("Desktop integration was successful.") },
|
||||
{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10,
|
||||
|
@ -218,6 +218,7 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
|
|||
if (m_state == EState::FadingOut) {
|
||||
push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), true, m_current_fade_opacity);
|
||||
fading_pop = true;
|
||||
}
|
||||
|
||||
|
@ -229,7 +230,7 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
|
|||
m_id = m_id_provider.allocate_id();
|
||||
std::string name = "!!Ntfctn" + std::to_string(m_id);
|
||||
|
||||
if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) {
|
||||
if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
ImVec2 win_size = ImGui::GetWindowSize();
|
||||
|
||||
render_left_sign(imgui);
|
||||
|
@ -245,7 +246,7 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
|
|||
ImGui::PopStyleColor();
|
||||
|
||||
if (fading_pop)
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
bool NotificationManager::PopNotification::push_background_color()
|
||||
{
|
||||
|
@ -440,9 +441,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
|
|||
close();
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
//hover color
|
||||
ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);
|
||||
|
@ -501,11 +500,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img
|
|||
{
|
||||
close();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||
|
@ -545,11 +540,7 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper&
|
|||
m_multiline = false;
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
m_minimize_b_visible = true;
|
||||
}
|
||||
bool NotificationManager::PopNotification::on_text_click()
|
||||
|
@ -790,11 +781,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW
|
|||
wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
|
||||
close();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
bool NotificationManager::ExportFinishedNotification::on_text_click()
|
||||
{
|
||||
|
@ -1054,11 +1041,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu
|
|||
{
|
||||
wxGetApp().printhost_job_queue().cancel(m_job_id - 1);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
//------UpdatedItemsInfoNotification-------
|
||||
void NotificationManager::UpdatedItemsInfoNotification::count_spaces()
|
||||
|
@ -1074,10 +1057,39 @@ void NotificationManager::UpdatedItemsInfoNotification::count_spaces()
|
|||
m_window_width_offset = m_left_indentation + m_line_height * 3.f;
|
||||
m_window_width = m_line_height * 25;
|
||||
}
|
||||
void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType type)
|
||||
{
|
||||
std::vector<std::pair<InfoItemType, size_t>>::iterator it = m_types_and_counts.begin();
|
||||
for (; it != m_types_and_counts.end(); ++it) {
|
||||
if ((*it).first == type) {
|
||||
(*it).second++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (it == m_types_and_counts.end())
|
||||
m_types_and_counts.emplace_back(type, 1);
|
||||
|
||||
std::string text;
|
||||
for (it = m_types_and_counts.begin(); it != m_types_and_counts.end(); ++it) {
|
||||
text += std::to_string((*it).second);
|
||||
text += _L_PLURAL(" Object was loaded with "," Objects were loaded with ", (*it).second).ToUTF8().data();
|
||||
switch ((*it).first) {
|
||||
case InfoItemType::CustomSupports: text += _utf8("custom supports.\n"); break;
|
||||
case InfoItemType::CustomSeam: text += _utf8("custom seam.\n"); break;
|
||||
case InfoItemType::MmuSegmentation: text += _utf8("multimaterial painting.\n"); break;
|
||||
case InfoItemType::VariableLayerHeight: text += _utf8("variable layer height.\n"); break;
|
||||
case InfoItemType::Sinking: text += _utf8("Partial sinking.\n"); break;
|
||||
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
|
||||
}
|
||||
}
|
||||
NotificationData data { get_data().type, get_data().level , get_data().duration, text };
|
||||
update(data);
|
||||
}
|
||||
void NotificationManager::UpdatedItemsInfoNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||
{
|
||||
std::string text;
|
||||
switch (m_info_item_type) {
|
||||
InfoItemType type = (m_types_and_counts.empty() ? InfoItemType::CustomSupports : m_types_and_counts[0].first);
|
||||
switch (type) {
|
||||
case InfoItemType::CustomSupports: text = ImGui::CustomSupportsMarker; break;
|
||||
case InfoItemType::CustomSeam: text = ImGui::CustomSeamMarker; break;
|
||||
case InfoItemType::MmuSegmentation: text = ImGui::MmuSegmentationMarker; break;
|
||||
|
@ -1347,31 +1359,43 @@ void NotificationManager::upload_job_notification_show_error(int id, const std::
|
|||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::push_hint_notification()
|
||||
void NotificationManager::push_hint_notification(bool open_next)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::DidYouKnowHint) {
|
||||
(dynamic_cast<HintNotification*>(notification.get()))->open_next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 300, "" };
|
||||
push_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), 0);
|
||||
}
|
||||
|
||||
bool NotificationManager::is_hint_notification_open()
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::DidYouKnowHint)
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 0, "" };
|
||||
push_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler), 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
void NotificationManager::push_updated_item_info_notification(InfoItemType type)
|
||||
{
|
||||
std::string text = _utf8("Object(s) were loaded with ");
|
||||
switch (type) {
|
||||
case InfoItemType::CustomSupports: text += _utf8("custom supports."); break;
|
||||
case InfoItemType::CustomSeam: text += _utf8("custom seam."); break;
|
||||
case InfoItemType::MmuSegmentation: text += _utf8("MMU segmentation."); break;
|
||||
case InfoItemType::VariableLayerHeight: text += _utf8("variable layer height."); break;
|
||||
case InfoItemType::Sinking: text = _utf8("Partially sinking object(s) were loaded."); break;
|
||||
default: text.clear(); break;
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::UpdatedItemsInfo) {
|
||||
(dynamic_cast<UpdatedItemsInfoNotification*>(notification.get()))->add_type(type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!text.empty()) {
|
||||
NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotification, 10, text };
|
||||
push_notification_data(std::make_unique<NotificationManager::UpdatedItemsInfoNotification>(data, m_id_provider, m_evt_handler, type), 0);
|
||||
|
||||
NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotification, 5, "" };
|
||||
auto notification = std::make_unique<NotificationManager::UpdatedItemsInfoNotification>(data, m_id_provider, m_evt_handler, type);
|
||||
if (push_notification_data(std::move(notification), 0)) {
|
||||
(dynamic_cast<UpdatedItemsInfoNotification*>(m_pop_notifications.back().get()))->add_type(type);
|
||||
}
|
||||
|
||||
}
|
||||
bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp)
|
||||
{
|
||||
|
|
|
@ -96,7 +96,9 @@ enum class NotificationType
|
|||
DidYouKnowHint,
|
||||
// Shows when ObjectList::update_info_items finds information that should be stressed to the user
|
||||
// Might contain logo taken from gizmos
|
||||
UpdatedItemsInfo
|
||||
UpdatedItemsInfo,
|
||||
// Give user advice to simplify object with big amount of triangles
|
||||
SimplifySuggestion
|
||||
};
|
||||
|
||||
class NotificationManager
|
||||
|
@ -169,7 +171,8 @@ public:
|
|||
void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host);
|
||||
void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host);
|
||||
// Hint (did you know) notification
|
||||
void push_hint_notification();
|
||||
void push_hint_notification(bool open_next);
|
||||
bool is_hint_notification_open();
|
||||
void push_updated_item_info_notification(InfoItemType type);
|
||||
// Close old notification ExportFinished.
|
||||
void new_export_began(bool on_removable);
|
||||
|
@ -390,7 +393,7 @@ private:
|
|||
{
|
||||
public:
|
||||
|
||||
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { set_percentage(percentage); }
|
||||
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { }
|
||||
virtual void set_percentage(float percent) { m_percentage = percent; }
|
||||
protected:
|
||||
virtual void init() override;
|
||||
|
@ -433,6 +436,7 @@ private:
|
|||
, m_file_size(filesize)
|
||||
{
|
||||
m_has_cancel_button = true;
|
||||
set_percentage(percentage);
|
||||
}
|
||||
static std::string get_upload_job_text(int id, const std::string& filename, const std::string& host) { return /*"[" + std::to_string(id) + "] " + */filename + " -> " + host; }
|
||||
void set_percentage(float percent) override;
|
||||
|
@ -498,13 +502,14 @@ private:
|
|||
public:
|
||||
UpdatedItemsInfoNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, InfoItemType info_item_type)
|
||||
: PopNotification(n, id_provider, evt_handler)
|
||||
, m_info_item_type(info_item_type)
|
||||
{
|
||||
//m_types_and_counts.emplace_back(info_item_type, 1);
|
||||
}
|
||||
void count_spaces() override;
|
||||
void add_type(InfoItemType type);
|
||||
protected:
|
||||
void render_left_sign(ImGuiWrapper& imgui) override;
|
||||
InfoItemType m_info_item_type;
|
||||
std::vector<std::pair<InfoItemType, size_t>> m_types_and_counts;
|
||||
};
|
||||
|
||||
// in HintNotification.hpp
|
||||
|
@ -535,7 +540,13 @@ private:
|
|||
// Timestamp of last rendering
|
||||
int64_t m_last_render { 0LL };
|
||||
// Notification types that can be shown multiple types at once (compared by text)
|
||||
const std::vector<NotificationType> m_multiple_types = { NotificationType::CustomNotification, NotificationType::PlaterWarning, NotificationType::ProgressBar, NotificationType::PrintHostUpload, NotificationType::UpdatedItemsInfo };
|
||||
const std::vector<NotificationType> m_multiple_types = {
|
||||
NotificationType::CustomNotification,
|
||||
NotificationType::PlaterWarning,
|
||||
NotificationType::ProgressBar,
|
||||
NotificationType::PrintHostUpload,
|
||||
NotificationType::SimplifySuggestion
|
||||
};
|
||||
//prepared (basic) notifications
|
||||
static const NotificationData basic_notifications[];
|
||||
};
|
||||
|
|
|
@ -37,7 +37,21 @@ void ObjectDataViewModelNode::init_container()
|
|||
static constexpr char LayerRootIcon[] = "edit_layers_all";
|
||||
static constexpr char LayerIcon[] = "edit_layers_some";
|
||||
static constexpr char WarningIcon[] = "exclamation";
|
||||
static constexpr char InfoIcon[] = "info";
|
||||
static constexpr char InfoIcon[] = "objlist_info";
|
||||
|
||||
struct InfoItemAtributes {
|
||||
std::string name;
|
||||
std::string bmp_name;
|
||||
};
|
||||
|
||||
const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||
// info_item Type info_item Name info_item BitmapName
|
||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports" }, },
|
||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam" }, },
|
||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation"}, },
|
||||
{ InfoItemType::Sinking, {L("Sinking"), "support_blocker"}, },
|
||||
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
|
||||
};
|
||||
|
||||
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const wxString& sub_obj_name,
|
||||
|
@ -60,14 +74,10 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare
|
|||
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const InfoItemType info_type) :
|
||||
m_parent(parent),
|
||||
m_type(itInfo),
|
||||
m_extruder(wxEmptyString)
|
||||
m_info_item_type(info_type),
|
||||
m_extruder(wxEmptyString),
|
||||
m_name(_(INFO_ITEMS.at(info_type).name))
|
||||
{
|
||||
m_name = info_type == InfoItemType::CustomSupports ? _L("Paint-on supports") :
|
||||
info_type == InfoItemType::CustomSeam ? _L("Paint-on seam") :
|
||||
info_type == InfoItemType::MmuSegmentation ? _L("Paint-on segmentation") :
|
||||
info_type == InfoItemType::Sinking ? _L("Sinking") :
|
||||
_L("Variable layer height");
|
||||
m_info_item_type = info_type;
|
||||
}
|
||||
|
||||
|
||||
|
@ -307,7 +317,8 @@ ObjectDataViewModel::ObjectDataViewModel()
|
|||
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_info_bmp = create_scaled_bitmap(InfoIcon);
|
||||
for (auto item : INFO_ITEMS)
|
||||
m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name);
|
||||
}
|
||||
|
||||
ObjectDataViewModel::~ObjectDataViewModel()
|
||||
|
@ -402,7 +413,7 @@ wxDataViewItem ObjectDataViewModel::AddInfoChild(const wxDataViewItem &parent_it
|
|||
}
|
||||
|
||||
root->Insert(node, idx+1);
|
||||
node->SetBitmap(m_info_bmp);
|
||||
node->SetBitmap(m_info_bmps.at(info_type));
|
||||
// notify control
|
||||
const wxDataViewItem child((void*)node);
|
||||
ItemAdded(parent_item, child);
|
||||
|
@ -1130,7 +1141,7 @@ void ObjectDataViewModel::GetItemInfo(const wxDataViewItem& item, ItemType& type
|
|||
if (!node ||
|
||||
node->GetIdx() <-1 ||
|
||||
( node->GetIdx() == -1 &&
|
||||
!(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot/* | itLayer*/))
|
||||
!(node->GetType() & (itObject | itSettings | itInstanceRoot | itLayerRoot | itInfo))
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
@ -1494,7 +1505,17 @@ void ObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataVie
|
|||
}
|
||||
}
|
||||
|
||||
ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const
|
||||
bool ObjectDataViewModel::HasInfoItem(InfoItemType type) const
|
||||
{
|
||||
for (ObjectDataViewModelNode* obj_node : m_objects)
|
||||
for (size_t j = 0; j < obj_node->GetChildCount(); j++)
|
||||
if (obj_node->GetNthChild(j)->GetInfoItemType() == type)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const
|
||||
{
|
||||
if (!item.IsOk())
|
||||
return itUndef;
|
||||
|
@ -1697,6 +1718,24 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty
|
|||
return *bmp;
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item)
|
||||
{
|
||||
if (!item.IsOk())
|
||||
return;
|
||||
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
|
||||
|
||||
if (node->GetType() & itObject) {
|
||||
node->SetBitmap(m_warning_bmp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->GetType() & itVolume) {
|
||||
node->SetBitmap(GetVolumeIcon(node->GetVolumeType(), true));
|
||||
node->GetParent()->SetBitmap(m_warning_bmp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/)
|
||||
{
|
||||
if (!item.IsOk())
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <wx/dataview.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "ExtraRenderers.hpp"
|
||||
|
||||
|
@ -251,8 +252,8 @@ class ObjectDataViewModel :public wxDataViewModel
|
|||
{
|
||||
std::vector<ObjectDataViewModelNode*> m_objects;
|
||||
std::vector<wxBitmap> m_volume_bmps;
|
||||
std::map<InfoItemType, wxBitmap> m_info_bmps;
|
||||
wxBitmap m_warning_bmp;
|
||||
wxBitmap m_info_bmp;
|
||||
|
||||
wxDataViewCtrl* m_ctrl { nullptr };
|
||||
|
||||
|
@ -345,6 +346,7 @@ public:
|
|||
// Is the container just a header or an item with all columns
|
||||
// In our case it is an item with all columns
|
||||
bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
|
||||
bool HasInfoItem(InfoItemType type) const;
|
||||
|
||||
ItemType GetItemType(const wxDataViewItem &item) const;
|
||||
InfoItemType GetInfoItemType(const wxDataViewItem &item) const;
|
||||
|
@ -376,6 +378,7 @@ public:
|
|||
|
||||
wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
|
||||
const bool is_marked = false);
|
||||
void AddWarningIcon(const wxDataViewItem& item);
|
||||
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
|
||||
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;
|
||||
|
||||
|
|
|
@ -88,11 +88,6 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
|
|||
if (!m_disabled)
|
||||
this->on_kill_focus(opt_id);
|
||||
};
|
||||
field->m_on_set_focus = [this](const std::string& opt_id) {
|
||||
//! This function will be called from Field.
|
||||
if (!m_disabled)
|
||||
this->on_set_focus(opt_id);
|
||||
};
|
||||
field->m_parent = parent();
|
||||
|
||||
field->m_back_to_initial_value = [this](std::string opt_id) {
|
||||
|
@ -514,12 +509,6 @@ void OptionsGroup::clear_fields_except_of(const std::vector<std::string> left_fi
|
|||
}
|
||||
}
|
||||
|
||||
void OptionsGroup::on_set_focus(const std::string& opt_key)
|
||||
{
|
||||
if (m_set_focus != nullptr)
|
||||
m_set_focus(opt_key);
|
||||
}
|
||||
|
||||
void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) {
|
||||
if (m_on_change != nullptr)
|
||||
m_on_change(opt_id, value);
|
||||
|
|
|
@ -100,7 +100,6 @@ public:
|
|||
// To be called when the field loses focus, to assign a new initial value to the field.
|
||||
// Used by the relative position / rotation / scale manipulation fields of the Object Manipulation UI.
|
||||
t_kill_focus m_fill_empty_value { nullptr };
|
||||
t_kill_focus m_set_focus { nullptr };
|
||||
std::function<DynamicPrintConfig()> m_get_initial_config{ nullptr };
|
||||
std::function<DynamicPrintConfig()> m_get_sys_config{ nullptr };
|
||||
std::function<bool()> have_sys_config{ nullptr };
|
||||
|
@ -208,7 +207,6 @@ protected:
|
|||
const t_field& build_field(const Option& opt);
|
||||
|
||||
virtual void on_kill_focus(const std::string& opt_key) {};
|
||||
virtual void on_set_focus(const std::string& opt_key);
|
||||
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
|
||||
virtual void back_to_initial_value(const std::string& opt_key) {}
|
||||
virtual void back_to_sys_value(const std::string& opt_key) {}
|
||||
|
|
|
@ -396,6 +396,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
|||
m_optgroup->append_line(cafile_hint);
|
||||
}
|
||||
else {
|
||||
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
|
||||
|
@ -411,7 +412,6 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
|||
sizer->Add(txt, 1, wxEXPAND);
|
||||
return sizer;
|
||||
};
|
||||
|
||||
m_optgroup->append_line(line);
|
||||
}
|
||||
|
||||
|
@ -421,6 +421,12 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
|
|||
m_optgroup->append_single_option_line(option);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
option = m_optgroup->get_option("printhost_ssl_ignore_revoke");
|
||||
option.opt.width = Field::def_width_wider();
|
||||
m_optgroup->append_single_option_line(option);
|
||||
#endif
|
||||
|
||||
m_optgroup->activate();
|
||||
|
||||
Field* printhost_field = m_optgroup->get_field("print_host");
|
||||
|
|
|
@ -1576,20 +1576,19 @@ struct Plater::priv
|
|||
|
||||
bool is_project_dirty() const { return dirty_state.is_dirty(); }
|
||||
void update_project_dirty_from_presets() { dirty_state.update_from_presets(); }
|
||||
bool save_project_if_dirty() {
|
||||
int save_project_if_dirty() {
|
||||
int res = wxID_NO;
|
||||
if (dirty_state.is_dirty()) {
|
||||
MainFrame* mainframe = wxGetApp().mainframe;
|
||||
if (mainframe->can_save_as()) {
|
||||
//wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
|
||||
MessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
|
||||
int res = dlg.ShowModal();
|
||||
res = dlg.ShowModal();
|
||||
if (res == wxID_YES)
|
||||
mainframe->save_project_as(wxGetApp().plater()->get_project_filename());
|
||||
else if (res == wxID_CANCEL)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return res;
|
||||
}
|
||||
void reset_project_dirty_after_save() { dirty_state.reset_after_save(); }
|
||||
void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); }
|
||||
|
@ -1659,6 +1658,7 @@ struct Plater::priv
|
|||
void deselect_all();
|
||||
void remove(size_t obj_idx);
|
||||
void delete_object_from_model(size_t obj_idx);
|
||||
void delete_all_objects_from_model();
|
||||
void reset();
|
||||
void mirror(Axis axis);
|
||||
void split_object();
|
||||
|
@ -1721,7 +1721,7 @@ struct Plater::priv
|
|||
void replace_with_stl();
|
||||
void reload_all_from_disk();
|
||||
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
|
||||
|
||||
void create_simplify_notification(const std::vector<size_t>& obj_ids);
|
||||
void set_current_panel(wxPanel* panel);
|
||||
|
||||
void on_select_preset(wxCommandEvent&);
|
||||
|
@ -1771,6 +1771,7 @@ struct Plater::priv
|
|||
bool can_arrange() const;
|
||||
bool can_layers_editing() const;
|
||||
bool can_fix_through_netfabb() const;
|
||||
bool can_simplify() const;
|
||||
bool can_set_instance_to_object() const;
|
||||
bool can_mirror() const;
|
||||
bool can_reload_from_disk() const;
|
||||
|
@ -1944,7 +1945,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
// 3DScene/Toolbar:
|
||||
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_DELETE_ALL, [this](SimpleEvent&) { delete_all_objects_from_model(); });
|
||||
// view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); });
|
||||
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(); });
|
||||
|
@ -2261,12 +2263,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
// and place the loaded config over the base.
|
||||
config += std::move(config_loaded);
|
||||
}
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO:
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (! config_substitutions.empty())
|
||||
show_substitutions_info(config_substitutions.substitutions, filename.string());
|
||||
|
||||
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
|
||||
}
|
||||
|
@ -2338,9 +2336,15 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
else {
|
||||
model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion));
|
||||
for (auto obj : model.objects)
|
||||
if (obj->name.empty())
|
||||
if (obj->name.empty() ||
|
||||
obj->name.find_first_of("/") != std::string::npos) // When file is imported from Fusion360 the path containes "/" instead of "\\" (see https://github.com/prusa3d/PrusaSlicer/issues/6803)
|
||||
// But read_from_file doesn't support that direction separator and as a result object name containes full path
|
||||
obj->name = fs::path(obj->input_file).filename().string();
|
||||
}
|
||||
} catch (const ConfigurationError &e) {
|
||||
std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what();
|
||||
GUI::show_error(q, message);
|
||||
continue;
|
||||
} catch (const std::exception &e) {
|
||||
GUI::show_error(q, e.what());
|
||||
continue;
|
||||
|
@ -2360,25 +2364,23 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
// Convert even if the object is big.
|
||||
convert_from_imperial_units(model, false);
|
||||
else if (model.looks_like_saved_in_meters()) {
|
||||
//wxMessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
|
||||
MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The object in file %s looks like saved in meters.\n"
|
||||
"Should I consider it as a saved in meters and convert it?",
|
||||
"Some objects in file %s look like saved in meters.\n"
|
||||
"Should I consider them as a saved in meters and convert them?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object appears to be saved in meters"), wxICON_WARNING | wxYES | wxNO);
|
||||
"The dimensions of the object from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in meters.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES)
|
||||
//FIXME up-scale only the small parts?
|
||||
model.convert_from_meters(true);
|
||||
}
|
||||
else if (model.looks_like_imperial_units()) {
|
||||
//wxMessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
|
||||
MessageDialog msg_dlg(q, format_wxstr(_L_PLURAL(
|
||||
"The object in file %s looks like saved in inches.\n"
|
||||
"Should I consider it as a saved in inches and convert it?",
|
||||
"Some objects in file %s look like saved in inches.\n"
|
||||
"Should I consider them as a saved in inches and convert them?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object appears to be saved in inches"), wxICON_WARNING | wxYES | wxNO);
|
||||
"The dimensions of the object from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?",
|
||||
"The dimensions of some objects from file %s seem to be defined in inches.\n"
|
||||
"The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n",
|
||||
_L("The object is too small"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES)
|
||||
//FIXME up-scale only the small parts?
|
||||
convert_from_imperial_units(model, true);
|
||||
|
@ -2388,8 +2390,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
//wxMessageDialog msg_dlg(q, _L(
|
||||
MessageDialog msg_dlg(q, _L(
|
||||
"This file contains several objects positioned at multiple heights.\n"
|
||||
"Instead of considering them as multiple objects, should I consider\n"
|
||||
"this file as a single object having multiple parts?") + "\n",
|
||||
"Instead of considering them as multiple objects, should \n"
|
||||
"should the file be loaded as a single object having multiple parts?") + "\n",
|
||||
_L("Multi-part object detected"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (msg_dlg.ShowModal() == wxID_YES) {
|
||||
model.convert_multipart_object(nozzle_dmrs->values.size());
|
||||
|
@ -2426,6 +2428,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
}
|
||||
|
||||
if (one_by_one) {
|
||||
if (type_3mf && !is_project_file)
|
||||
model.center_instances_around_point(bed_shape_bb().center());
|
||||
auto loaded_idxs = load_model_objects(model.objects, is_project_file);
|
||||
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
|
||||
} else {
|
||||
|
@ -2474,6 +2478,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
view3D->get_canvas3d()->update_gizmos_on_off_state();
|
||||
}
|
||||
|
||||
create_simplify_notification(obj_idxs);
|
||||
|
||||
return obj_idxs;
|
||||
}
|
||||
|
||||
|
@ -2563,6 +2569,10 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
|||
_L("Object too large?"));
|
||||
}
|
||||
|
||||
// Now ObjectList uses GLCanvas3D::is_object_sinkin() to show/hide "Sinking" InfoItem,
|
||||
// so 3D-scene should be updated before object additing to the ObjectList
|
||||
this->view3D->reload_scene(false, (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
|
||||
|
||||
for (const size_t idx : obj_idxs) {
|
||||
wxGetApp().obj_list()->add_object_to_list(idx);
|
||||
}
|
||||
|
@ -2747,6 +2757,32 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
|
|||
object_list_changed();
|
||||
}
|
||||
|
||||
void Plater::priv::delete_all_objects_from_model()
|
||||
{
|
||||
Plater::TakeSnapshot snapshot(q, _L("Delete All Objects"));
|
||||
|
||||
if (view3D->is_layers_editing_enabled())
|
||||
view3D->enable_layers_editing(false);
|
||||
|
||||
reset_gcode_toolpaths();
|
||||
gcode_result.reset();
|
||||
|
||||
view3D->get_canvas3d()->reset_sequential_print_clearance();
|
||||
|
||||
// Stop and reset the Print content.
|
||||
background_process.reset();
|
||||
model.clear_objects();
|
||||
update();
|
||||
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
|
||||
sidebar->obj_list()->delete_all_objects_from_list();
|
||||
object_list_changed();
|
||||
|
||||
// The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here
|
||||
sidebar->show_sliced_info_sizer(false);
|
||||
|
||||
model.custom_gcode_per_print_z.gcodes.clear();
|
||||
}
|
||||
|
||||
void Plater::priv::reset()
|
||||
{
|
||||
Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
|
||||
|
@ -2938,6 +2974,9 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
|
|||
if (view3D->is_layers_editing_enabled())
|
||||
view3D->get_wxglcanvas()->Refresh();
|
||||
|
||||
if (background_process.empty())
|
||||
view3D->get_canvas3d()->reset_sequential_print_clearance();
|
||||
|
||||
if (invalidated == Print::APPLY_STATUS_INVALIDATED) {
|
||||
// Some previously calculated data on the Print was invalidated.
|
||||
// Hide the slicing results, as the current slicing status is no more valid.
|
||||
|
@ -3211,9 +3250,10 @@ void Plater::priv::replace_with_stl()
|
|||
new_volume->set_material_id(old_volume->material_id());
|
||||
new_volume->set_transformation(old_volume->get_transformation());
|
||||
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
|
||||
assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters);
|
||||
if (old_volume->source.is_converted_from_inches)
|
||||
new_volume->convert_from_imperial_units();
|
||||
if (old_volume->source.is_converted_from_meters)
|
||||
else if (old_volume->source.is_converted_from_meters)
|
||||
new_volume->convert_from_meters();
|
||||
new_volume->supported_facets.assign(old_volume->supported_facets);
|
||||
new_volume->seam_facets.assign(old_volume->seam_facets);
|
||||
|
@ -3419,13 +3459,11 @@ void Plater::priv::reload_from_disk()
|
|||
new_volume->set_material_id(old_volume->material_id());
|
||||
new_volume->set_transformation(old_volume->get_transformation());
|
||||
new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset));
|
||||
assert(! old_volume->source.is_converted_from_inches || ! old_volume->source.is_converted_from_meters);
|
||||
if (old_volume->source.is_converted_from_inches)
|
||||
new_volume->convert_from_imperial_units();
|
||||
if (old_volume->source.is_converted_from_meters)
|
||||
else if (old_volume->source.is_converted_from_meters)
|
||||
new_volume->convert_from_meters();
|
||||
new_volume->supported_facets.assign(old_volume->supported_facets);
|
||||
new_volume->seam_facets.assign(old_volume->seam_facets);
|
||||
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
|
||||
std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back());
|
||||
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
|
||||
if (!sinking)
|
||||
|
@ -3496,44 +3534,59 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
|
|||
// size_t snapshot_time = undo_redo_stack().active_snapshot_time();
|
||||
Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb"));
|
||||
|
||||
q->clear_before_change_mesh(obj_idx);
|
||||
ModelObject* mo = model.objects[obj_idx];
|
||||
|
||||
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
|
||||
// may be different and they would make no sense.
|
||||
bool paint_removed = false;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
|
||||
mv->supported_facets.clear();
|
||||
mv->seam_facets.clear();
|
||||
mv->mmu_segmentation_facets.clear();
|
||||
}
|
||||
if (paint_removed) {
|
||||
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
|
||||
notification_manager->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("Custom supports and seams were removed after repairing the mesh."));
|
||||
// _u8L("Undo the repair"),
|
||||
// [this, snapshot_time](wxEvtHandler*){
|
||||
// // Make sure the snapshot is still available and that
|
||||
// // we are in the main stack and not in a gizmo-stack.
|
||||
// if (undo_redo_stack().has_undo_snapshot(snapshot_time)
|
||||
// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
|
||||
// undo_redo_to(snapshot_time);
|
||||
// else
|
||||
// notification_manager->push_notification(
|
||||
// NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
// NotificationManager::NotificationLevel::RegularNotification,
|
||||
// _u8L("Cannot undo to before the mesh repair!"));
|
||||
// return true;
|
||||
// });
|
||||
}
|
||||
|
||||
fix_model_by_win10_sdk_gui(*mo, vol_idx);
|
||||
sla::reproject_points_and_holes(mo);
|
||||
this->update();
|
||||
this->object_list_changed();
|
||||
this->schedule_background_process();
|
||||
q->changed_mesh(obj_idx);
|
||||
// workaround to fix the issue, when PrusaSlicer lose a focus after model fixing
|
||||
q->SetFocus();
|
||||
}
|
||||
|
||||
void Plater::priv::create_simplify_notification(const std::vector<size_t>& obj_ids) {
|
||||
const uint32_t triangles_to_suggest_simplify = 1000000;
|
||||
|
||||
std::vector<size_t> big_ids;
|
||||
big_ids.reserve(obj_ids.size());
|
||||
std::copy_if(obj_ids.begin(), obj_ids.end(), std::back_inserter(big_ids),
|
||||
[this, triangles_to_suggest_simplify](size_t object_id) {
|
||||
if (object_id >= model.objects.size()) return false; // out of object index
|
||||
ModelVolumePtrs& volumes = model.objects[object_id]->volumes;
|
||||
if (volumes.size() != 1) return false; // not only one volume
|
||||
size_t triangle_count = volumes.front()->mesh().its.indices.size();
|
||||
if (triangle_count < triangles_to_suggest_simplify) return false; // small volume
|
||||
return true;
|
||||
});
|
||||
|
||||
if (big_ids.empty()) return;
|
||||
|
||||
for (size_t object_id : big_ids) {
|
||||
std::string t = _u8L(
|
||||
"Processing model '@object_name' with more than 1M triangles "
|
||||
"could be slow. It is highly recommend to reduce "
|
||||
"amount of triangles.");
|
||||
t.replace(t.find("@object_name"), sizeof("@object_name") - 1,
|
||||
model.objects[object_id]->name);
|
||||
std::stringstream text;
|
||||
text << _u8L("WARNING:") << "\n" << t << "\n";
|
||||
std::string hypertext = _u8L("Simplify model");
|
||||
|
||||
std::function<bool(wxEvtHandler *)> open_simplify = [object_id](wxEvtHandler *) {
|
||||
auto plater = wxGetApp().plater();
|
||||
if (object_id >= plater->model().objects.size()) return true;
|
||||
|
||||
Selection &selection = plater->canvas3D()->get_selection();
|
||||
selection.clear();
|
||||
selection.add_object((unsigned int) object_id);
|
||||
|
||||
auto &manager = plater->canvas3D()->get_gizmos_manager();
|
||||
manager.open_gizmo(GLGizmosManager::EType::Simplify);
|
||||
return true;
|
||||
};
|
||||
notification_manager->push_notification(
|
||||
NotificationType::SimplifySuggestion,
|
||||
NotificationManager::NotificationLevel::WarningNotification,
|
||||
text.str(), hypertext, open_simplify);
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::set_current_panel(wxPanel* panel)
|
||||
|
@ -3952,6 +4005,10 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
|
|||
if (evt.data.second)
|
||||
return;
|
||||
|
||||
// Each context menu respects to the selected item in ObjectList,
|
||||
// so this selection should be updated before menu creation
|
||||
wxGetApp().obj_list()->update_selections();
|
||||
|
||||
if (printer_technology == ptSLA)
|
||||
menu = menus.sla_object_menu();
|
||||
else {
|
||||
|
@ -4305,6 +4362,18 @@ bool Plater::priv::can_fix_through_netfabb() const
|
|||
return model.objects[obj_idx]->get_mesh_errors_count() > 0;
|
||||
}
|
||||
|
||||
|
||||
bool Plater::priv::can_simplify() const
|
||||
{
|
||||
// is object for simplification selected
|
||||
if (get_selected_object_idx() < 0) return false;
|
||||
// is already opened?
|
||||
if (q->canvas3D()->get_gizmos_manager().get_current_type() ==
|
||||
GLGizmosManager::EType::Simplify)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Plater::priv::can_increase_instances() const
|
||||
{
|
||||
if (m_ui_jobs.is_any_running()
|
||||
|
@ -4667,7 +4736,7 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame)
|
|||
|
||||
bool Plater::is_project_dirty() const { return p->is_project_dirty(); }
|
||||
void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); }
|
||||
bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); }
|
||||
int Plater::save_project_if_dirty() { return p->save_project_if_dirty(); }
|
||||
void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); }
|
||||
void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); }
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
|
@ -4720,10 +4789,8 @@ void Plater::load_project(const wxString& filename)
|
|||
std::vector<fs::path> input_paths;
|
||||
input_paths.push_back(into_path(filename));
|
||||
|
||||
std::vector<size_t> res = load_files(input_paths);
|
||||
|
||||
// if res is empty no data has been loaded
|
||||
if (!res.empty()) {
|
||||
if (! load_files(input_paths).empty()) {
|
||||
// At least one file was loaded.
|
||||
p->set_project_filename(filename);
|
||||
reset_project_dirty_initial_presets();
|
||||
update_project_dirty_from_presets();
|
||||
|
@ -4758,8 +4825,7 @@ void Plater::add_model(bool imperial_units/* = false*/)
|
|||
}
|
||||
|
||||
Plater::TakeSnapshot snapshot(this, snapshot_label);
|
||||
std::vector<size_t> res = load_files(paths, true, false, imperial_units);
|
||||
if (!res.empty())
|
||||
if (! load_files(paths, true, false, imperial_units).empty())
|
||||
wxGetApp().mainframe->update_title();
|
||||
}
|
||||
|
||||
|
@ -4893,7 +4959,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
|
|||
main_sizer->Add(new wxStaticText(this, wxID_ANY,
|
||||
_L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10);
|
||||
|
||||
int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
|
||||
m_action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
|
||||
static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1;
|
||||
|
||||
wxStaticBox* action_stb = new wxStaticBox(this, wxID_ANY, _L("Action"));
|
||||
|
@ -4904,7 +4970,7 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
|
|||
int id = 0;
|
||||
for (const wxString& label : choices) {
|
||||
wxRadioButton* btn = new wxRadioButton(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0);
|
||||
btn->SetValue(id == action);
|
||||
btn->SetValue(id == m_action);
|
||||
btn->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { m_action = id; });
|
||||
stb_sizer->Add(btn, 0, wxEXPAND | wxTOP, 5);
|
||||
id++;
|
||||
|
@ -5322,13 +5388,14 @@ void Plater::export_gcode(bool prefer_removable)
|
|||
|
||||
fs::path output_path;
|
||||
{
|
||||
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 file as:"),
|
||||
std::string ext = default_output_file.extension().string();
|
||||
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"),
|
||||
start_dir,
|
||||
from_path(default_output_file.filename()),
|
||||
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
|
||||
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : boost::iequals(ext, ".sl1s") ? FT_SL1S : FT_SL1, ext),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
|
||||
);
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
output_path = into_path(dlg.GetPath());
|
||||
}
|
||||
|
||||
|
@ -5865,6 +5932,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
|
|||
p->sidebar->update_searcher();
|
||||
p->sidebar->show_sliced_info_sizer(false);
|
||||
p->reset_gcode_toolpaths();
|
||||
p->view3D->get_canvas3d()->reset_sequential_print_clearance();
|
||||
}
|
||||
else if (opt_key == "bed_shape" || opt_key == "bed_custom_texture" || opt_key == "bed_custom_model") {
|
||||
bed_shape_changed = true;
|
||||
|
@ -6136,13 +6204,58 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology)
|
|||
return ret;
|
||||
}
|
||||
|
||||
void Plater::clear_before_change_mesh(int obj_idx)
|
||||
{
|
||||
ModelObject* mo = model().objects[obj_idx];
|
||||
|
||||
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
|
||||
// may be different and they would make no sense.
|
||||
bool paint_removed = false;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
|
||||
mv->supported_facets.clear();
|
||||
mv->seam_facets.clear();
|
||||
mv->mmu_segmentation_facets.clear();
|
||||
}
|
||||
if (paint_removed) {
|
||||
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
|
||||
get_notification_manager()->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("Custom supports, seams and multimaterial painting were "
|
||||
"removed after repairing the mesh."));
|
||||
// _u8L("Undo the repair"),
|
||||
// [this, snapshot_time](wxEvtHandler*){
|
||||
// // Make sure the snapshot is still available and that
|
||||
// // we are in the main stack and not in a gizmo-stack.
|
||||
// if (undo_redo_stack().has_undo_snapshot(snapshot_time)
|
||||
// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
|
||||
// undo_redo_to(snapshot_time);
|
||||
// else
|
||||
// notification_manager->push_notification(
|
||||
// NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
// NotificationManager::NotificationLevel::RegularNotification,
|
||||
// _u8L("Cannot undo to before the mesh repair!"));
|
||||
// return true;
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::changed_mesh(int obj_idx)
|
||||
{
|
||||
ModelObject* mo = model().objects[obj_idx];
|
||||
sla::reproject_points_and_holes(mo);
|
||||
update();
|
||||
p->object_list_changed();
|
||||
p->schedule_background_process();
|
||||
}
|
||||
|
||||
void Plater::changed_object(int obj_idx)
|
||||
{
|
||||
if (obj_idx < 0)
|
||||
return;
|
||||
// recenter and re - align to Z = 0
|
||||
auto model_object = p->model.objects[obj_idx];
|
||||
model_object->ensure_on_bed(this->p->printer_technology != ptSLA);
|
||||
p->model.objects[obj_idx]->ensure_on_bed(p->printer_technology != ptSLA);
|
||||
if (this->p->printer_technology == ptSLA) {
|
||||
// Update the SLAPrint from the current Model, so that the reload_scene()
|
||||
// pulls the correct data, update the 3D scene.
|
||||
|
@ -6403,6 +6516,7 @@ bool Plater::can_increase_instances() const { return p->can_increase_instances()
|
|||
bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); }
|
||||
bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); }
|
||||
bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); }
|
||||
bool Plater::can_simplify() const { return p->can_simplify(); }
|
||||
bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); }
|
||||
bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); }
|
||||
bool Plater::can_arrange() const { return p->can_arrange(); }
|
||||
|
|
|
@ -140,7 +140,7 @@ public:
|
|||
|
||||
bool is_project_dirty() const;
|
||||
void update_project_dirty_from_presets();
|
||||
bool save_project_if_dirty();
|
||||
int save_project_if_dirty();
|
||||
void reset_project_dirty_after_save();
|
||||
void reset_project_dirty_initial_presets();
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
|
@ -226,6 +226,10 @@ public:
|
|||
void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false);
|
||||
void reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages = false);
|
||||
void reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false);
|
||||
|
||||
void clear_before_change_mesh(int obj_idx);
|
||||
void changed_mesh(int obj_idx);
|
||||
|
||||
void changed_object(int obj_idx);
|
||||
void changed_objects(const std::vector<size_t>& object_idxs);
|
||||
void schedule_background_process(bool schedule = true);
|
||||
|
@ -306,6 +310,7 @@ public:
|
|||
bool can_decrease_instances() const;
|
||||
bool can_set_instance_to_object() const;
|
||||
bool can_fix_through_netfabb() const;
|
||||
bool can_simplify() const;
|
||||
bool can_split_to_objects() const;
|
||||
bool can_split_to_volumes() const;
|
||||
bool can_arrange() const;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <wx/notebook.h>
|
||||
#include "Notebook.hpp"
|
||||
#include "ButtonsDescription.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
@ -342,7 +343,7 @@ void PreferencesDialog::build(size_t selected_tab)
|
|||
m_optgroup_gui->append_single_option_line(option);
|
||||
#endif
|
||||
|
||||
def.label = L("Show \"Did you know\" hints after start");
|
||||
def.label = L("Show \"Tip of the day\" notification after start");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If enabled, useful hints are displayed at startup.");
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->get("show_hints") == "1" });
|
||||
|
@ -395,7 +396,8 @@ void PreferencesDialog::build(size_t selected_tab)
|
|||
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
for (int id : {wxID_OK, wxID_CANCEL})
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(id, this)));
|
||||
|
||||
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10);
|
||||
|
||||
|
@ -638,32 +640,7 @@ void PreferencesDialog::create_settings_text_color_widget()
|
|||
if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
wxSizer* sizer = new wxStaticBoxSizer(stb, wxVERTICAL);
|
||||
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(2, 5, 5);
|
||||
sizer->Add(grid_sizer, 0, wxEXPAND);
|
||||
|
||||
auto sys_label = new wxStaticText(parent, wxID_ANY, _L("Value is the same as the system value"));
|
||||
sys_label->SetForegroundColour(wxGetApp().get_label_clr_sys());
|
||||
m_sys_colour = new wxColourPickerCtrl(parent, wxID_ANY, wxGetApp().get_label_clr_sys());
|
||||
wxGetApp().UpdateDarkUI(m_sys_colour->GetPickerCtrl(), true);
|
||||
m_sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, [this, sys_label](wxCommandEvent&) {
|
||||
sys_label->SetForegroundColour(m_sys_colour->GetColour());
|
||||
sys_label->Refresh();
|
||||
});
|
||||
|
||||
grid_sizer->Add(m_sys_colour, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(sys_label, 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
||||
|
||||
auto mod_label = new wxStaticText(parent, wxID_ANY, _L("Value was changed and is not equal to the system value or the last saved preset"));
|
||||
mod_label->SetForegroundColour(wxGetApp().get_label_clr_modified());
|
||||
m_mod_colour = new wxColourPickerCtrl(parent, wxID_ANY, wxGetApp().get_label_clr_modified());
|
||||
wxGetApp().UpdateDarkUI(m_mod_colour->GetPickerCtrl(), true);
|
||||
m_mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, [this, mod_label](wxCommandEvent&) {
|
||||
mod_label->SetForegroundColour(m_mod_colour->GetColour());
|
||||
mod_label->Refresh();
|
||||
});
|
||||
|
||||
grid_sizer->Add(m_mod_colour, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(mod_label, 0, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
|
||||
ButtonsDescription::FillSizerWithTextColorDescriptions(sizer, parent, &m_sys_colour, &m_mod_colour);
|
||||
|
||||
m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit());
|
||||
}
|
||||
|
|
|
@ -634,6 +634,12 @@ PlaterPresetComboBox::~PlaterPresetComboBox()
|
|||
edit_btn->Destroy();
|
||||
}
|
||||
|
||||
static void run_wizard(ConfigWizard::StartPage sp)
|
||||
{
|
||||
if (wxGetApp().check_and_save_current_preset_changes())
|
||||
wxGetApp().run_wizard(ConfigWizard::RR_USER, sp);
|
||||
}
|
||||
|
||||
void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
|
||||
{
|
||||
auto selected_item = evt.GetSelection();
|
||||
|
@ -653,7 +659,7 @@ void PlaterPresetComboBox::OnSelect(wxCommandEvent &evt)
|
|||
case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break;
|
||||
default: break;
|
||||
}
|
||||
wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); });
|
||||
wxTheApp->CallAfter([sp]() { run_wizard(sp); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -685,7 +691,7 @@ void PlaterPresetComboBox::show_add_menu()
|
|||
|
||||
append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "",
|
||||
[](wxCommandEvent&) {
|
||||
wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); });
|
||||
wxTheApp->CallAfter([]() { run_wizard(ConfigWizard::SP_PRINTERS); });
|
||||
}, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
|
||||
|
||||
append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "",
|
||||
|
@ -715,7 +721,7 @@ void PlaterPresetComboBox::show_edit_menu()
|
|||
else
|
||||
append_menu_item(menu, wxID_ANY, _L("Add/Remove presets"), "",
|
||||
[](wxCommandEvent&) {
|
||||
wxTheApp->CallAfter([]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS); });
|
||||
wxTheApp->CallAfter([]() { run_wizard(ConfigWizard::SP_PRINTERS); });
|
||||
}, "edit_uni", menu, []() { return true; }, wxGetApp().plater());
|
||||
|
||||
append_menu_item(menu, wxID_ANY, _L("Add physical printer"), "",
|
||||
|
@ -918,7 +924,7 @@ void TabPresetComboBox::OnSelect(wxCommandEvent &evt)
|
|||
this->SetSelection(m_last_selected);
|
||||
if (marker == LABEL_ITEM_WIZARD_PRINTERS)
|
||||
wxTheApp->CallAfter([this]() {
|
||||
wxGetApp().run_wizard(ConfigWizard::RR_USER, ConfigWizard::SP_PRINTERS);
|
||||
run_wizard(ConfigWizard::SP_PRINTERS);
|
||||
|
||||
// update combobox if its parent is a PhysicalPrinterDialog
|
||||
PhysicalPrinterDialog* parent = dynamic_cast<PhysicalPrinterDialog*>(this->GetParent());
|
||||
|
|
|
@ -28,7 +28,7 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac
|
|||
const size_t active_snapshot_time = stack.active_snapshot_time();
|
||||
const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time));
|
||||
const int idx = it - snapshots.begin() - 1;
|
||||
const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && (size_t)idx < snapshots.size() - 1) ?
|
||||
const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && idx < int(snapshots.size()) - 1) ?
|
||||
&snapshots[idx] : nullptr;
|
||||
|
||||
assert(ret != nullptr);
|
||||
|
@ -195,8 +195,7 @@ void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type)
|
|||
void ProjectDirtyStateManager::update_from_presets()
|
||||
{
|
||||
m_state.presets = false;
|
||||
std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets();
|
||||
for (const auto& [type, name] : selected_presets) {
|
||||
for (const auto& [type, name] : wxGetApp().get_selected_presets()) {
|
||||
m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
|
||||
}
|
||||
m_state.presets |= wxGetApp().has_unsaved_preset_changes();
|
||||
|
@ -214,6 +213,7 @@ void ProjectDirtyStateManager::reset_after_save()
|
|||
m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
|
||||
}
|
||||
else {
|
||||
// Gizmo is active with its own Undo / Redo stack (for example the SLA support point editing gizmo).
|
||||
const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack);
|
||||
if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) {
|
||||
if (m_state.gizmos.current)
|
||||
|
@ -231,8 +231,7 @@ void ProjectDirtyStateManager::reset_after_save()
|
|||
void ProjectDirtyStateManager::reset_initial_presets()
|
||||
{
|
||||
m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>();
|
||||
std::vector<std::pair<unsigned int, std::string>> selected_presets = wxGetApp().get_selected_presets();
|
||||
for (const auto& [type, name] : selected_presets) {
|
||||
for (const auto& [type, name] : wxGetApp().get_selected_presets()) {
|
||||
m_initial_presets[type] = name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -683,7 +683,8 @@ void Selection::translate(const Vec3d& displacement, bool local)
|
|||
synchronize_unselected_volumes();
|
||||
#endif // !DISABLE_INSTANCES_SYNCH
|
||||
|
||||
this->set_bounding_boxes_dirty();
|
||||
ensure_not_below_bed();
|
||||
set_bounding_boxes_dirty();
|
||||
}
|
||||
|
||||
// Rotate an object around one of the axes. Only one rotation component is expected to be changing.
|
||||
|
@ -1148,6 +1149,7 @@ void Selection::erase()
|
|||
}
|
||||
|
||||
wxGetApp().obj_list()->delete_from_model_and_list(items);
|
||||
ensure_not_below_bed();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1712,7 +1714,7 @@ void Selection::calc_unscaled_instance_bounding_box() const
|
|||
if (volume.is_modifier)
|
||||
continue;
|
||||
Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, true, false) * volume.get_volume_transformation().get_matrix();
|
||||
trafo.translation()(2) += volume.get_sla_shift_z();
|
||||
trafo.translation().z() += volume.get_sla_shift_z();
|
||||
unscaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo));
|
||||
}
|
||||
}
|
||||
|
@ -1729,7 +1731,7 @@ void Selection::calc_scaled_instance_bounding_box() const
|
|||
if (volume.is_modifier)
|
||||
continue;
|
||||
Transform3d trafo = volume.get_instance_transformation().get_matrix(false, false, false, false) * volume.get_volume_transformation().get_matrix();
|
||||
trafo.translation()(2) += volume.get_sla_shift_z();
|
||||
trafo.translation().z() += volume.get_sla_shift_z();
|
||||
scaled_instance_bounding_box->merge(volume.transformed_convex_hull_bounding_box(trafo));
|
||||
}
|
||||
}
|
||||
|
@ -2134,6 +2136,44 @@ void Selection::ensure_on_bed()
|
|||
}
|
||||
}
|
||||
|
||||
void Selection::ensure_not_below_bed()
|
||||
{
|
||||
typedef std::map<std::pair<int, int>, double> InstancesToZMap;
|
||||
InstancesToZMap instances_max_z;
|
||||
|
||||
for (size_t i = 0; i < m_volumes->size(); ++i) {
|
||||
GLVolume* volume = (*m_volumes)[i];
|
||||
if (!volume->is_wipe_tower && !volume->is_modifier) {
|
||||
const double max_z = volume->transformed_convex_hull_bounding_box().max.z();
|
||||
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
|
||||
InstancesToZMap::iterator it = instances_max_z.find(instance);
|
||||
if (it == instances_max_z.end())
|
||||
it = instances_max_z.insert(InstancesToZMap::value_type(instance, -DBL_MAX)).first;
|
||||
|
||||
it->second = std::max(it->second, max_z);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_any_volume()) {
|
||||
for (unsigned int i : m_list) {
|
||||
GLVolume& volume = *(*m_volumes)[i];
|
||||
std::pair<int, int> instance = std::make_pair(volume.object_idx(), volume.instance_idx());
|
||||
InstancesToZMap::iterator it = instances_max_z.find(instance);
|
||||
double z_shift = SINKING_MIN_Z_THRESHOLD - it->second;
|
||||
if (it != instances_max_z.end() && z_shift > 0.0)
|
||||
volume.set_volume_offset(Z, volume.get_volume_offset(Z) + z_shift);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (GLVolume* volume : *m_volumes) {
|
||||
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
|
||||
InstancesToZMap::iterator it = instances_max_z.find(instance);
|
||||
if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD)
|
||||
volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const
|
||||
{
|
||||
struct SameInstance
|
||||
|
|
|
@ -385,6 +385,7 @@ public:
|
|||
|
||||
private:
|
||||
void ensure_on_bed();
|
||||
void ensure_not_below_bed();
|
||||
bool is_from_fully_selected_instance(unsigned int volume_idx) const;
|
||||
|
||||
void paste_volumes_from_clipboard();
|
||||
|
|
|
@ -1518,11 +1518,11 @@ void TabPrint::build()
|
|||
optgroup->append_single_option_line("support_material_auto", category_path + "auto-generated-supports");
|
||||
optgroup->append_single_option_line("support_material_threshold", category_path + "overhang-threshold");
|
||||
optgroup->append_single_option_line("support_material_enforce_layers", category_path + "enforce-support-for-the-first");
|
||||
optgroup->append_single_option_line("raft_first_layer_density", category_path + "raft-first-layer-density");
|
||||
optgroup->append_single_option_line("raft_first_layer_expansion", category_path + "raft-first-layer-expansion");
|
||||
|
||||
optgroup = page->new_optgroup(L("Raft"));
|
||||
optgroup->append_single_option_line("raft_layers", category_path + "raft-layers");
|
||||
optgroup->append_single_option_line("raft_first_layer_density", category_path + "raft-first-layer-density");
|
||||
optgroup->append_single_option_line("raft_first_layer_expansion", category_path + "raft-first-layer-expansion");
|
||||
optgroup->append_single_option_line("raft_contact_distance");
|
||||
optgroup->append_single_option_line("raft_expansion");
|
||||
|
||||
|
@ -1747,14 +1747,14 @@ bool Tab::validate_custom_gcode(const wxString& title, const std::string& gcode)
|
|||
std::vector<std::string> tags;
|
||||
bool invalid = GCodeProcessor::contains_reserved_tags(gcode, 5, tags);
|
||||
if (invalid) {
|
||||
wxString reports = _L_PLURAL("The following line", "The following lines", tags.size());
|
||||
reports += ":\n";
|
||||
for (const std::string& keyword : tags) {
|
||||
reports += ";" + keyword + "\n";
|
||||
}
|
||||
reports += _L("contain reserved keywords.") + "\n";
|
||||
reports += _L("Please remove them, as they may cause problems in g-code visualization and printing time estimation.");
|
||||
|
||||
std::string lines = ":\n";
|
||||
for (const std::string& keyword : tags)
|
||||
lines += ";" + keyword + "\n";
|
||||
wxString reports = format_wxstr(
|
||||
_L_PLURAL("The following line %s contains reserved keywords.\nPlease remove it, as it may cause problems in G-code visualization and printing time estimation.",
|
||||
"The following lines %s contain reserved keywords.\nPlease remove them, as they may cause problems in G-code visualization and printing time estimation.",
|
||||
tags.size()),
|
||||
lines);
|
||||
//wxMessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(wxGetApp().mainframe, reports, _L("Found reserved keywords in") + " " + _(title), wxICON_WARNING | wxOK);
|
||||
dialog.ShowModal();
|
||||
|
@ -2208,6 +2208,7 @@ void TabPrinter::build_fff()
|
|||
def.label = L("Extruders");
|
||||
def.tooltip = L("Number of extruders of the printer.");
|
||||
def.min = 1;
|
||||
def.max = 256;
|
||||
def.mode = comExpert;
|
||||
Option option(def, "extruders_count");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
|
|
@ -85,8 +85,11 @@ bool MsgUpdateSlic3r::disable_version_check() const
|
|||
|
||||
// MsgUpdateConfig
|
||||
|
||||
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
|
||||
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
|
||||
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) :
|
||||
MsgDialog(nullptr, force_before_wizard ? _L("Opening Configuration Wizard") : _L("Configuration update"),
|
||||
force_before_wizard ? _L("PrusaSlicer is not using the newest configuration available.\n"
|
||||
"Configuration Wizard may not offer the latest printers, filaments and SLA materials to be installed. ") :
|
||||
_L("Configuration update is available"), wxID_NONE)
|
||||
{
|
||||
auto *text = new wxStaticText(this, wxID_ANY, _(L(
|
||||
"Would you like to install it?\n\n"
|
||||
|
@ -130,11 +133,17 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
|
|||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(2*VERT_SPACING);
|
||||
|
||||
auto *btn_cancel = new wxButton(this, wxID_CANCEL);
|
||||
btn_sizer->Add(btn_cancel);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
auto *btn_ok = new wxButton(this, wxID_OK);
|
||||
auto* btn_ok = new wxButton(this, wxID_OK, force_before_wizard ? _L("Install") : "OK");
|
||||
btn_sizer->Add(btn_ok);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
if (force_before_wizard) {
|
||||
auto* btn_no_install = new wxButton(this, wxID_ANY, "Don't install");
|
||||
btn_no_install->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CLOSE); });
|
||||
btn_sizer->Add(btn_no_install);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
}
|
||||
auto* btn_cancel = new wxButton(this, wxID_CANCEL);
|
||||
btn_sizer->Add(btn_cancel);
|
||||
btn_ok->SetFocus();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
|
|
@ -54,7 +54,8 @@ public:
|
|||
{}
|
||||
};
|
||||
|
||||
MsgUpdateConfig(const std::vector<Update> &updates);
|
||||
// force_before_wizard - indicates that check of updated is forced before ConfigWizard opening
|
||||
MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard = false);
|
||||
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
|
||||
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
|
||||
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;
|
||||
|
|
|
@ -36,8 +36,6 @@
|
|||
#include "../GUI/GUI.hpp"
|
||||
#include "../GUI/I18N.hpp"
|
||||
#include "../GUI/MsgDialog.hpp"
|
||||
#include "../GUI/GUI_App.hpp"
|
||||
#include "../GUI/Mainframe.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/progdlg.h>
|
||||
|
@ -343,7 +341,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
|
|||
wxProgressDialog progress_dialog(
|
||||
_L("Model fixing"),
|
||||
_L("Exporting model") + "...",
|
||||
100, GUI::wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
|
||||
100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); // ! parent of the wxProgressDialog should be nullptr to avoid flickering during the model fixing
|
||||
// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
|
||||
// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
|
||||
bool success = false;
|
||||
|
@ -364,9 +362,17 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
|
|||
boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
path_src += ".3mf";
|
||||
Model model;
|
||||
ModelObject *model_object = model.add_object();
|
||||
model_object->add_volume(*volumes[ivolume]);
|
||||
model_object->add_instance();
|
||||
ModelObject *mo = model.add_object();
|
||||
mo->add_volume(*volumes[ivolume]);
|
||||
|
||||
// We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back.
|
||||
// store_3mf currently bakes the volume transformation into the mesh itself.
|
||||
// If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume
|
||||
// (which remembers the matrix the whole time), the transformation would be used twice.
|
||||
// We will therefore set the volume transform on the dummy ModelVolume to identity.
|
||||
mo->volumes.back()->set_transformation(Geometry::Transformation());
|
||||
|
||||
mo->add_instance();
|
||||
if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) {
|
||||
boost::filesystem::remove(path_src);
|
||||
throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed"));
|
||||
|
|
|
@ -19,6 +19,7 @@ static HexFile::DeviceKind parse_device_kind(const std::string &str)
|
|||
else if (str == "mk3") { return HexFile::DEV_MK3; }
|
||||
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
|
||||
else if (str == "cw1") { return HexFile::DEV_CW1; }
|
||||
else if (str == "cw1s") { return HexFile::DEV_CW1S; }
|
||||
else { return HexFile::DEV_GENERIC; }
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ struct HexFile
|
|||
DEV_MK3,
|
||||
DEV_MM_CONTROL,
|
||||
DEV_CW1,
|
||||
DEV_CW1S,
|
||||
};
|
||||
|
||||
boost::filesystem::path path;
|
||||
|
|
|
@ -491,6 +491,18 @@ Http& Http::form_add_file(const std::string &name, const fs::path &path, const s
|
|||
return *this;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
|
||||
// This option is only supported for Schannel (the native Windows SSL library).
|
||||
Http& Http::ssl_revoke_best_effort(bool set)
|
||||
{
|
||||
if(p && set){
|
||||
::curl_easy_setopt(p->curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
Http& Http::set_post_body(const fs::path &path)
|
||||
{
|
||||
if (p) { p->set_post_body(path);}
|
||||
|
|
|
@ -80,6 +80,12 @@ public:
|
|||
// Same as above except also override the file's filename with a custom one
|
||||
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
|
||||
|
||||
#ifdef WIN32
|
||||
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
|
||||
// This option is only supported for Schannel (the native Windows SSL library).
|
||||
Http& ssl_revoke_best_effort(bool set);
|
||||
#endif // WIN32
|
||||
|
||||
// Set the file contents as a POST request body.
|
||||
// The data is used verbatim, it is not additionally encoded in any way.
|
||||
// This can be used for hosts which do not support multipart requests.
|
||||
|
|
|
@ -23,9 +23,10 @@ namespace pt = boost::property_tree;
|
|||
namespace Slic3r {
|
||||
|
||||
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
||||
host(config->opt_string("print_host")),
|
||||
apikey(config->opt_string("printhost_apikey")),
|
||||
cafile(config->opt_string("printhost_cafile"))
|
||||
m_host(config->opt_string("print_host")),
|
||||
m_apikey(config->opt_string("printhost_apikey")),
|
||||
m_cafile(config->opt_string("printhost_cafile")),
|
||||
m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
|
||||
{}
|
||||
|
||||
const char* OctoPrint::get_name() const { return "OctoPrint"; }
|
||||
|
@ -73,6 +74,9 @@ bool OctoPrint::test(wxString &msg) const
|
|||
msg = "Could not parse server response";
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
|
@ -137,6 +141,9 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
|
|||
res = false;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
|
@ -149,31 +156,31 @@ bool OctoPrint::validate_version_text(const boost::optional<std::string> &versio
|
|||
|
||||
void OctoPrint::set_auth(Http &http) const
|
||||
{
|
||||
http.header("X-Api-Key", apikey);
|
||||
http.header("X-Api-Key", m_apikey);
|
||||
|
||||
if (! cafile.empty()) {
|
||||
http.ca_file(cafile);
|
||||
if (!m_cafile.empty()) {
|
||||
http.ca_file(m_cafile);
|
||||
}
|
||||
}
|
||||
|
||||
std::string OctoPrint::make_url(const std::string &path) const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % host % path).str();
|
||||
if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
|
||||
if (m_host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % m_host % path).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%") % host % path).str();
|
||||
return (boost::format("%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||
return (boost::format("http://%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
}
|
||||
|
||||
SL1Host::SL1Host(DynamicPrintConfig *config) :
|
||||
OctoPrint(config),
|
||||
authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
username(config->opt_string("printhost_user")),
|
||||
password(config->opt_string("printhost_password"))
|
||||
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
m_username(config->opt_string("printhost_user")),
|
||||
m_password(config->opt_string("printhost_password"))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -182,7 +189,7 @@ const char* SL1Host::get_name() const { return "SL1Host"; }
|
|||
|
||||
wxString SL1Host::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to Prusa SL1 works correctly."));
|
||||
return _(L("Connection to Prusa SL1 / SL1S works correctly."));
|
||||
}
|
||||
|
||||
wxString SL1Host::get_test_failed_msg (wxString &msg) const
|
||||
|
@ -199,12 +206,12 @@ bool SL1Host::validate_version_text(const boost::optional<std::string> &version_
|
|||
|
||||
void SL1Host::set_auth(Http &http) const
|
||||
{
|
||||
switch (authorization_type) {
|
||||
switch (m_authorization_type) {
|
||||
case atKeyPassword:
|
||||
http.header("X-Api-Key", get_apikey());
|
||||
break;
|
||||
case atUserPassword:
|
||||
http.auth_digest(username, password);
|
||||
http.auth_digest(m_username, m_password);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -216,9 +223,9 @@ void SL1Host::set_auth(Http &http) const
|
|||
// PrusaLink
|
||||
PrusaLink::PrusaLink(DynamicPrintConfig* config) :
|
||||
OctoPrint(config),
|
||||
authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
username(config->opt_string("printhost_user")),
|
||||
password(config->opt_string("printhost_password"))
|
||||
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
m_username(config->opt_string("printhost_user")),
|
||||
m_password(config->opt_string("printhost_password"))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -243,12 +250,12 @@ bool PrusaLink::validate_version_text(const boost::optional<std::string>& versio
|
|||
|
||||
void PrusaLink::set_auth(Http& http) const
|
||||
{
|
||||
switch (authorization_type) {
|
||||
switch (m_authorization_type) {
|
||||
case atKeyPassword:
|
||||
http.header("X-Api-Key", get_apikey());
|
||||
break;
|
||||
case atUserPassword:
|
||||
http.auth_digest(username, password);
|
||||
http.auth_digest(m_username, m_password);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,17 +29,18 @@ public:
|
|||
bool has_auto_discovery() const override { return true; }
|
||||
bool can_test() const override { return true; }
|
||||
bool can_start_print() const override { return true; }
|
||||
std::string get_host() const override { return host; }
|
||||
const std::string& get_apikey() const { return apikey; }
|
||||
const std::string& get_cafile() const { return cafile; }
|
||||
std::string get_host() const override { return m_host; }
|
||||
const std::string& get_apikey() const { return m_apikey; }
|
||||
const std::string& get_cafile() const { return m_cafile; }
|
||||
|
||||
protected:
|
||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
|
||||
private:
|
||||
std::string host;
|
||||
std::string apikey;
|
||||
std::string cafile;
|
||||
std::string m_host;
|
||||
std::string m_apikey;
|
||||
std::string m_cafile;
|
||||
bool m_ssl_revoke_best_effort;
|
||||
|
||||
virtual void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
|
@ -64,10 +65,10 @@ private:
|
|||
void set_auth(Http &http) const override;
|
||||
|
||||
// Host authorization type.
|
||||
AuthorizationType authorization_type;
|
||||
AuthorizationType m_authorization_type;
|
||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
};
|
||||
|
||||
class PrusaLink : public OctoPrint
|
||||
|
@ -89,10 +90,10 @@ private:
|
|||
void set_auth(Http& http) const override;
|
||||
|
||||
// Host authorization type.
|
||||
AuthorizationType authorization_type;
|
||||
AuthorizationType m_authorization_type;
|
||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string m_username;
|
||||
std::string m_password;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -65,6 +65,10 @@ void copy_file_fix(const fs::path &source, const fs::path &target)
|
|||
_L("Copying of file %1% to %2% failed: %3%"),
|
||||
source, target, error_message));
|
||||
}
|
||||
// Permissions should be copied from the source file by copy_file(). We are not sure about the source
|
||||
// permissions, let's rewrite them with 644.
|
||||
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
||||
fs::permissions(target, perms);
|
||||
}
|
||||
|
||||
struct Update
|
||||
|
@ -167,7 +171,7 @@ struct PresetUpdater::priv
|
|||
|
||||
void check_install_indices() const;
|
||||
Updates get_config_updates(const Semver& old_slic3r_version) const;
|
||||
void perform_updates(Updates &&updates, bool snapshot = true) const;
|
||||
bool perform_updates(Updates &&updates, bool snapshot = true) const;
|
||||
void set_waiting_updates(Updates u);
|
||||
};
|
||||
|
||||
|
@ -580,12 +584,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
|||
return updates;
|
||||
}
|
||||
|
||||
void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
|
||||
bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
|
||||
{
|
||||
if (updates.incompats.size() > 0) {
|
||||
if (snapshot) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
|
||||
SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE);
|
||||
if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "",
|
||||
_u8L("Continue and install configuration updates?")))
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% incompatible bundles", updates.incompats.size());
|
||||
|
@ -600,7 +606,9 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
|
||||
if (snapshot) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
|
||||
SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE);
|
||||
if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "",
|
||||
_u8L("Continue and install configuration updates?")))
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size());
|
||||
|
@ -611,7 +619,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
update.install();
|
||||
|
||||
PresetBundle bundle;
|
||||
bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem);
|
||||
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
|
||||
bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
|
||||
|
||||
|
@ -643,6 +652,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresetUpdater::priv::set_waiting_updates(Updates u)
|
||||
|
@ -715,12 +726,14 @@ static void reload_configs_update_gui()
|
|||
auto* app_config = GUI::wxGetApp().app_config;
|
||||
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
|
||||
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
// However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air.
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
GUI::wxGetApp().plater()->set_bed_shape();
|
||||
GUI::wxGetApp().update_wizard_from_config();
|
||||
}
|
||||
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
|
||||
{
|
||||
if (! p->enabled_config_update) { return R_NOOP; }
|
||||
|
||||
|
@ -754,11 +767,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
|
||||
// This effectively removes the incompatible bundles:
|
||||
// (snapshot is taken beforehand)
|
||||
p->perform_updates(std::move(updates));
|
||||
|
||||
if (!GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) {
|
||||
if (! p->perform_updates(std::move(updates)) ||
|
||||
! GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT))
|
||||
return R_INCOMPAT_EXIT;
|
||||
}
|
||||
|
||||
return R_INCOMPAT_CONFIGURED;
|
||||
}
|
||||
|
@ -792,7 +803,8 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(info) << "User wants to update...";
|
||||
p->perform_updates(std::move(updates));
|
||||
if (! p->perform_updates(std::move(updates)))
|
||||
return R_INCOMPAT_EXIT;
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
|
@ -803,7 +815,11 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
}
|
||||
|
||||
// regular update
|
||||
if (no_notification) {
|
||||
if (params == UpdateParams::SHOW_NOTIFICATION) {
|
||||
p->set_waiting_updates(updates);
|
||||
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
|
||||
|
||||
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
|
||||
|
@ -812,22 +828,22 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
|
||||
}
|
||||
|
||||
GUI::MsgUpdateConfig dlg(updates_msg);
|
||||
GUI::MsgUpdateConfig dlg(updates_msg, params == UpdateParams::FORCED_BEFORE_WIZARD);
|
||||
|
||||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
p->perform_updates(std::move(updates));
|
||||
if (! p->perform_updates(std::move(updates)))
|
||||
return R_ALL_CANCELED;
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "User refused the update";
|
||||
if (params == UpdateParams::FORCED_BEFORE_WIZARD && res == wxID_CANCEL)
|
||||
return R_ALL_CANCELED;
|
||||
return R_UPDATE_REJECT;
|
||||
}
|
||||
} else {
|
||||
p->set_waiting_updates(updates);
|
||||
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
|
||||
}
|
||||
|
||||
// MsgUpdateConfig will show after the notificaation is clicked
|
||||
|
@ -838,7 +854,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
return R_NOOP;
|
||||
}
|
||||
|
||||
void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
|
||||
bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
|
||||
{
|
||||
Updates updates;
|
||||
|
||||
|
@ -850,7 +866,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
|
|||
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
|
||||
}
|
||||
|
||||
p->perform_updates(std::move(updates), snapshot);
|
||||
return p->perform_updates(std::move(updates), snapshot);
|
||||
}
|
||||
|
||||
void PresetUpdater::on_update_notification_confirm()
|
||||
|
@ -870,16 +886,14 @@ void PresetUpdater::on_update_notification_confirm()
|
|||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
p->perform_updates(std::move(p->waiting_updates));
|
||||
reload_configs_update_gui();
|
||||
p->has_waiting_updates = false;
|
||||
//return R_UPDATE_INSTALLED;
|
||||
if (p->perform_updates(std::move(p->waiting_updates))) {
|
||||
reload_configs_update_gui();
|
||||
p->has_waiting_updates = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "User refused the update";
|
||||
//return R_UPDATE_REJECT;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,18 +35,24 @@ public:
|
|||
R_INCOMPAT_CONFIGURED,
|
||||
R_UPDATE_INSTALLED,
|
||||
R_UPDATE_REJECT,
|
||||
R_UPDATE_NOTIFICATION
|
||||
R_UPDATE_NOTIFICATION,
|
||||
R_ALL_CANCELED
|
||||
};
|
||||
|
||||
enum class UpdateParams {
|
||||
SHOW_TEXT_BOX, // force modal textbox
|
||||
SHOW_NOTIFICATION, // only shows notification
|
||||
FORCED_BEFORE_WIZARD // indicates that check of updated is forced before ConfigWizard opening
|
||||
};
|
||||
|
||||
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
|
||||
// A false return value implies Slic3r should exit due to incompatibility of configuration.
|
||||
// Providing old slic3r version upgrade profiles on upgrade of an application even in case
|
||||
// that the config index installed from the Internet is equal to the index contained in the installation package.
|
||||
// no_notification = force modal textbox, otherwise some cases only shows notification
|
||||
UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const;
|
||||
UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const;
|
||||
|
||||
// "Update" a list of bundles from resources (behaves like an online update).
|
||||
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
||||
void on_update_notification_confirm();
|
||||
private:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue