Merge branch 'master' into lm_sla_supports_auto2

This commit is contained in:
Lukas Matena 2019-02-04 08:41:00 +01:00
commit d32d0a7636
30 changed files with 363 additions and 286 deletions

View File

@ -717,6 +717,7 @@ void GCode::_do_export(Print &print, FILE *file)
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser();
m_placeholder_parser.update_timestamp();
print.update_object_placeholders(m_placeholder_parser.config_writable());
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
// For a print by objects, find the 1st printing object.

View File

@ -252,8 +252,10 @@ float new_feedrate_to_reach_time_stretch(
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
const CoolingLine &line = (*it)->lines[i];
if (line.feedrate > min_feedrate && line.feedrate < new_feedrate)
// Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate.
// Re-run the calculation with a new min_feedrate limit.
// Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate,
// which makes the new_feedrate lower than it should be.
// Re-run the calculation with a new min_feedrate limit, so that the segments with current feedrate lower than new_feedrate
// are not taken into account.
goto not_finished_yet;
}
goto finished;

View File

@ -547,13 +547,26 @@ void Model::reset_auto_extruder_id()
s_auto_extruder_id = 1;
}
std::string Model::propose_export_file_name() const
// Propose a filename including path derived from the ModelObject's input path.
// If object's name is filled in, use the object name, otherwise use the input name.
std::string Model::propose_export_file_name_and_path() const
{
std::string input_file;
for (const ModelObject *model_object : this->objects)
for (ModelInstance *model_instance : model_object->instances)
if (model_instance->is_printable()) {
input_file = model_object->name.empty() ? model_object->input_file : model_object->name;
input_file = model_object->input_file;
if (! model_object->name.empty()) {
if (input_file.empty())
// model_object->input_file was empty, just use model_object->name
input_file = model_object->name;
else {
// Replace file name in input_file with model_object->name, but keep the path and file extension.
input_file = (boost::filesystem::path(model_object->name).parent_path().empty()) ?
(boost::filesystem::path(input_file).parent_path() / model_object->name).make_preferred().string() :
model_object->name;
}
}
if (! input_file.empty())
goto end;
// Other instances will produce the same name, skip them.

View File

@ -387,6 +387,8 @@ public:
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
using ModelBase::set_new_unique_id;
protected:
friend class Print;
friend class SLAPrint;
@ -609,8 +611,8 @@ public:
static std::string get_auto_extruder_id_as_string(unsigned int max_extruders);
static void reset_auto_extruder_id();
// Propose an output file name based on the first printable object's name.
std::string propose_export_file_name() const;
// Propose an output file name & path based on the first printable object's name and source input file's path.
std::string propose_export_file_name_and_path() const;
private:
MODELBASE_DERIVED_PRIVATE_COPY_MOVE(Model)

View File

@ -32,7 +32,8 @@ public:
void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); }
void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); }
void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); }
const DynamicConfig& config() const { return m_config; }
DynamicConfig& config_writable() { return m_config; }
const DynamicConfig& config() const { return m_config; }
const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
// Fill in the template using a macro processing language.

View File

@ -421,8 +421,6 @@ void Print::add_model_object(ModelObject* model_object, int idx)
src_normalized.normalize();
object->config_apply(src_normalized, true);
}
this->update_object_placeholders();
}
bool Print::apply_config(DynamicPrintConfig config)
@ -1096,9 +1094,6 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
}
}
//FIXME there may be a race condition with the G-code export running at the background thread.
this->update_object_placeholders();
#ifdef _DEBUG
check_model_ids_equal(m_model, model);
#endif /* _DEBUG */
@ -1855,6 +1850,9 @@ int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion
std::max<int>(region.config().perimeter_extruder.value - 1, 0);
}
// Generate a recommended G-code output file name based on the format template, default extension, and template parameters
// (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics.
// Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized).
std::string Print::output_filename() const
{
// Set the placeholders for the data know first after the G-code export is finished.

View File

@ -15,7 +15,7 @@ namespace Slic3r
size_t PrintStateBase::g_last_timestamp = 0;
// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects.
void PrintBase::update_object_placeholders()
void PrintBase::update_object_placeholders(DynamicConfig &config) const
{
// get the first input file name
std::string input_file;
@ -33,27 +33,29 @@ void PrintBase::update_object_placeholders()
"% y:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Y) * 100) +
"% z:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Z) * 100) + "%");
if (input_file.empty())
input_file = model_object->input_file;
input_file = model_object->name.empty() ? model_object->input_file : model_object->name;
}
}
PlaceholderParser &pp = m_placeholder_parser;
pp.set("scale", v_scale);
config.set_key_value("year", new ConfigOptionStrings(v_scale));
if (! input_file.empty()) {
// get basename with and without suffix
const std::string input_basename = boost::filesystem::path(input_file).filename().string();
pp.set("input_filename", input_basename);
config.set_key_value("input_filename", new ConfigOptionString(input_basename));
const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of("."));
pp.set("input_filename_base", input_basename_base);
config.set_key_value("input_filename_base", new ConfigOptionString(input_basename_base));
}
}
// Generate an output file name based on the format template, default extension, and template parameters
// (timestamps, object placeholders derived from the model, current placeholder prameters, print statistics - config_override)
std::string PrintBase::output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override) const
{
DynamicConfig cfg;
if (config_override != nullptr)
cfg = *config_override;
PlaceholderParser::update_timestamp(cfg);
this->update_object_placeholders(cfg);
try {
boost::filesystem::path filename = this->placeholder_parser().process(format, 0, &cfg);
if (filename.extension().empty())
@ -69,7 +71,7 @@ std::string PrintBase::output_filepath(const std::string &path) const
// if we were supplied no path, generate an automatic one based on our first object's input file
if (path.empty())
// get the first input file name
return (boost::filesystem::path(m_model.propose_export_file_name()).parent_path() / this->output_filename()).make_preferred().string();
return (boost::filesystem::path(m_model.propose_export_file_name_and_path()).parent_path() / this->output_filename()).make_preferred().string();
// if we were supplied a directory, use it and append our automatically generated filename
boost::filesystem::path p(path);

View File

@ -309,7 +309,7 @@ protected:
// To be called by this->output_filename() with the format string pulled from the configuration layer.
std::string output_filename(const std::string &format, const std::string &default_ext, const DynamicConfig *config_override = nullptr) const;
// Update "scale", "input_filename", "input_filename_base" placeholders from the current printable ModelObjects.
void update_object_placeholders();
void update_object_placeholders(DynamicConfig &config) const;
Model m_model;

View File

@ -387,8 +387,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
update_apply_status(false);
}
this->update_object_placeholders();
#ifdef _DEBUG
check_model_ids_equal(m_model, model);
#endif /* _DEBUG */

View File

@ -8,6 +8,8 @@
#include "libslic3r.h"
namespace boost { namespace filesystem { class directory_entry; }}
namespace Slic3r {
extern void set_logging_level(unsigned int level);
@ -61,6 +63,12 @@ extern int rename_file(const std::string &from, const std::string &to);
// Copy a file, adjust the access attributes, so that the target is writable.
extern int copy_file(const std::string &from, const std::string &to);
// Ignore system and hidden files, which may be created by the DropBox synchronisation process.
// https://github.com/prusa3d/Slic3r/issues/1298
extern bool is_plain_file(const boost::filesystem::directory_entry &path);
extern bool is_ini_file(const boost::filesystem::directory_entry &path);
extern bool is_idx_file(const boost::filesystem::directory_entry &path);
// File path / name / extension splitting utilities, working with UTF-8,
// to be published to Perl.
namespace PerlUtils {

View File

@ -30,7 +30,13 @@
#include <tbb/task_scheduler_init.h>
#include <tbb/task_scheduler_init.h>
#if defined(__linux) || defined(__GNUC__ )
#include <strings.h>
#endif /* __linux */
#ifdef _MSC_VER
#define strcasecmp _stricmp
#endif
namespace Slic3r {
@ -248,6 +254,30 @@ int copy_file(const std::string &from, const std::string &to)
return 0;
}
// Ignore system and hidden files, which may be created by the DropBox synchronisation process.
// https://github.com/prusa3d/Slic3r/issues/1298
bool is_plain_file(const boost::filesystem::directory_entry &dir_entry)
{
if (! boost::filesystem::is_regular_file(dir_entry.status()))
return false;
#ifdef _MSC_VER
DWORD attributes = GetFileAttributesW(boost::nowide::widen(dir_entry.path().string()).c_str());
return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0;
#else
return true;
#endif
}
bool is_ini_file(const boost::filesystem::directory_entry &dir_entry)
{
return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".ini") == 0;
}
bool is_idx_file(const boost::filesystem::directory_entry &dir_entry)
{
return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".idx") == 0;
}
} // namespace Slic3r
#ifdef WIN32

View File

@ -252,10 +252,6 @@ int main(int argc, char **argv)
model.arrange_objects(fff_print.config().min_object_distance());
model.center_instances_around_point(cli_config.print_center);
}
if (outfile.empty()) {
outfile = model.propose_export_file_name();
outfile += (printer_technology == ptFFF) ? ".gcode" : ".zip";
}
if (printer_technology == ptFFF) {
for (auto* mo : model.objects)
fff_print.auto_assign_extruders(mo);
@ -265,6 +261,7 @@ int main(int argc, char **argv)
std::string err = print->validate();
if (err.empty()) {
if (printer_technology == ptFFF) {
// The outfile is processed by a PlaceholderParser.
fff_print.export_gcode(outfile, nullptr);
} else {
assert(printer_technology == ptSLA);

View File

@ -249,10 +249,10 @@ bool Snapshot::equal_to_active(const AppConfig &app_config) const
boost::filesystem::path path2 = snapshot_dir / subdir;
std::vector<std::string> files1, files2;
for (auto &dir_entry : boost::filesystem::directory_iterator(path1))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
if (Slic3r::is_ini_file(dir_entry))
files1.emplace_back(dir_entry.path().filename().string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path2))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
if (Slic3r::is_ini_file(dir_entry))
files2.emplace_back(dir_entry.path().filename().string());
std::sort(files1.begin(), files1.end());
std::sort(files2.begin(), files2.end());
@ -343,7 +343,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src
throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini"))
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);
}

View File

@ -297,7 +297,7 @@ std::vector<Index> Index::load_db()
std::vector<Index> index_db;
std::string errors_cummulative;
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_dir))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".idx")) {
if (Slic3r::is_idx_file(dir_entry)) {
Index idx;
try {
idx.load(dir_entry.path());

View File

@ -635,35 +635,32 @@ void ConfigWizard::priv::load_vendors()
const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles";
// Load vendors from the "vendors" directory in datadir
for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
for (auto &dir_entry : boost::filesystem::directory_iterator(vendor_dir))
if (Slic3r::is_ini_file(dir_entry)) {
try {
auto vp = VendorProfile::from_ini(it->path());
auto vp = VendorProfile::from_ini(dir_entry.path());
vendors[vp.id] = std::move(vp);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what();
}
}
}
// Additionally load up vendors from the application resources directory, but only those not seen in the datadir
for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
const auto id = it->path().stem().string();
for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_vendor_dir))
if (Slic3r::is_ini_file(dir_entry)) {
const auto id = dir_entry.path().stem().string();
if (vendors.find(id) == vendors.end()) {
try {
auto vp = VendorProfile::from_ini(it->path());
vendors_rsrc[vp.id] = it->path().filename().string();
auto vp = VendorProfile::from_ini(dir_entry.path());
vendors_rsrc[vp.id] = dir_entry.path().filename().string();
vendors[vp.id] = std::move(vp);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % dir_entry.path() % e.what();
}
}
}
}
// Load up the set of vendors / models / variants the user has had enabled up till now
const AppConfig *app_config = GUI::get_app_config();
@ -672,14 +669,15 @@ void ConfigWizard::priv::load_vendors()
} else {
// In case of legacy datadir, try to guess the preference based on the printer preset files that are present
const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) {
auto needle = legacy_preset_map.find(it->path().filename().string());
if (needle == legacy_preset_map.end()) { continue; }
for (auto &dir_entry : boost::filesystem::directory_iterator(printer_dir))
if (Slic3r::is_ini_file(dir_entry)) {
auto needle = legacy_preset_map.find(dir_entry.path().filename().string());
if (needle == legacy_preset_map.end()) { continue; }
const auto &model = needle->second.first;
const auto &variant = needle->second.second;
appconfig_vendors.set_variant("PrusaResearch", model, variant, true);
}
const auto &model = needle->second.first;
const auto &variant = needle->second.second;
appconfig_vendors.set_variant("PrusaResearch", model, variant, true);
}
}
}

View File

@ -3987,6 +3987,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
@ -4442,6 +4443,7 @@ void GLCanvas3D::update_toolbar_items_visibility()
ConfigOptionMode mode = wxGetApp().get_mode();
m_toolbar.set_item_visible("more", mode != comSimple);
m_toolbar.set_item_visible("fewer", mode != comSimple);
m_toolbar.set_item_visible("splitvolumes", mode != comSimple);
m_dirty = true;
}
#endif // ENABLE_MODE_AWARE_TOOLBAR_ITEMS
@ -5068,7 +5070,6 @@ void GLCanvas3D::bind_event_handlers()
m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this);
}
}
@ -5082,7 +5083,7 @@ void GLCanvas3D::unbind_event_handlers()
m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this);
@ -5094,7 +5095,6 @@ void GLCanvas3D::unbind_event_handlers()
m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key_down, this);
}
}
@ -5113,71 +5113,64 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
void GLCanvas3D::on_char(wxKeyEvent& evt)
{
if (evt.HasModifiers())
// see include/wx/defs.h enum wxKeyCode
int keyCode = evt.GetKeyCode();
int ctrlMask = wxMOD_CONTROL;
#ifdef __APPLE__
ctrlMask |= wxMOD_RAW_CONTROL;
#endif /* __APPLE__ */
if ((evt.GetModifiers() & ctrlMask) != 0) {
switch (keyCode) {
case WXK_CONTROL_A: post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL)); break;
#ifdef __APPLE__
case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
#endif /* __APPLE__ */
case WXK_DELETE: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break;
default: evt.Skip();
}
} else if (evt.HasModifiers()) {
evt.Skip();
else
{
int keyCode = evt.GetKeyCode();
switch (keyCode - 48)
} else {
switch (keyCode)
{
// numerical input
case 0: { select_view("iso"); break; }
case 1: { select_view("top"); break; }
case 2: { select_view("bottom"); break; }
case 3: { select_view("front"); break; }
case 4: { select_view("rear"); break; }
case 5: { select_view("left"); break; }
case 6: { select_view("right"); break; }
// key ESC
case WXK_ESCAPE: { m_gizmos.reset_all_states(); m_dirty = true; break; }
#ifdef __APPLE__
case WXK_BACK: // the low cost Apple solutions are not equipped with a Delete key, use Backspace instead.
#endif /* __APPLE__ */
case WXK_DELETE: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break;
case '0': { select_view("iso"); break; }
case '1': { select_view("top"); break; }
case '2': { select_view("bottom"); break; }
case '3': { select_view("front"); break; }
case '4': { select_view("rear"); break; }
case '5': { select_view("left"); break; }
case '6': { select_view("right"); break; }
case '+': { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; }
case '-': { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; }
case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
case 'A':
case 'a': { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; }
case 'B':
case 'b': { zoom_to_bed(); break; }
case 'I':
case 'i': { set_camera_zoom(1.0f); break; }
case 'O':
case 'o': { set_camera_zoom(-1.0f); break; }
case 'Z':
case 'z': { m_selection.is_empty() ? zoom_to_volumes() : zoom_to_selection(); break; }
default:
{
if (m_gizmos.handle_shortcut(keyCode, m_selection))
{
// text input
switch (keyCode)
{
// key ESC
case 27: { m_gizmos.reset_all_states(); m_dirty = true; break; }
// key +
case 43: { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break; }
// key -
case 45: { post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break; }
// key ?
case 63: { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
// key A/a
case 65:
case 97: { post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE)); break; }
// key B/b
case 66:
case 98: { zoom_to_bed(); break; }
// key I/i
case 73:
case 105: { set_camera_zoom(1.0f); break; }
// key O/o
case 79:
case 111: { set_camera_zoom(-1.0f); break; }
// key Z/z
case 90:
case 122:
{
if (m_selection.is_empty())
zoom_to_volumes();
else
zoom_to_selection();
break;
}
default:
{
if (m_gizmos.handle_shortcut(keyCode, m_selection))
{
_update_gizmos_data();
m_dirty = true;
}
else
evt.Skip();
break;
}
}
_update_gizmos_data();
m_dirty = true;
}
else
evt.Skip();
break;
}
}
}
}
@ -5692,24 +5685,6 @@ void GLCanvas3D::on_paint(wxPaintEvent& evt)
this->render();
}
void GLCanvas3D::on_key_down(wxKeyEvent& evt)
{
if (evt.HasModifiers())
evt.Skip();
else
{
int key = evt.GetKeyCode();
#ifdef __WXOSX__
if (key == WXK_BACK)
#else
if (key == WXK_DELETE)
#endif // __WXOSX__
post_event(SimpleEvent(EVT_GLCANVAS_REMOVE_OBJECT));
else
evt.Skip();
}
}
Size GLCanvas3D::get_canvas_size() const
{
int w = 0;
@ -6087,7 +6062,7 @@ bool GLCanvas3D::_init_toolbar()
GLToolbarItem::Data item;
item.name = "add";
item.tooltip = GUI::L_str("Add... [Ctrl+I]");
item.tooltip = GUI::L_str("Add...") + " [" + GUI::shortkey_ctrl_prefix() + "I]";
item.sprite_id = 0;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_ADD;
@ -6095,7 +6070,7 @@ bool GLCanvas3D::_init_toolbar()
return false;
item.name = "delete";
item.tooltip = GUI::L_str("Delete [Del]");
item.tooltip = GUI::L_str("Delete") + " [Del]";
item.sprite_id = 1;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_DELETE;
@ -6103,7 +6078,7 @@ bool GLCanvas3D::_init_toolbar()
return false;
item.name = "deleteall";
item.tooltip = GUI::L_str("Delete all [Ctrl+Del]");
item.tooltip = GUI::L_str("Delete all") + " [" + GUI::shortkey_ctrl_prefix() + "Del]";
item.sprite_id = 2;
item.is_toggable = false;
item.action_event = EVT_GLTOOLBAR_DELETE_ALL;

View File

@ -121,6 +121,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_VIEWPORT_CHANGED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, Vec2dEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>); // data: +1 => increase, -1 => decrease
wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
@ -1042,7 +1043,6 @@ public:
void on_timer(wxTimerEvent& evt);
void on_mouse(wxMouseEvent& evt);
void on_paint(wxPaintEvent& evt);
void on_key_down(wxKeyEvent& evt);
Size get_canvas_size() const;
Point get_local_mouse_position() const;

View File

@ -75,6 +75,30 @@ void break_to_debugger()
#endif /* _WIN32 */
}
const std::string& shortkey_ctrl_prefix()
{
static const std::string str =
#ifdef __APPLE__
""
#else
"Ctrl+"
#endif
;
return str;
}
const std::string& shortkey_alt_prefix()
{
static const std::string str =
#ifdef __APPLE__
""
#else
"Alt+"
#endif
;
return str;
}
bool config_wizard_startup(bool app_config_exists)
{
if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) {

View File

@ -27,7 +27,11 @@ void enable_screensaver();
bool debugged();
void break_to_debugger();
AppConfig* get_app_config();
// Platform specific Ctrl+/Alt+ (Windows, Linux) vs. ⌘/⌥ (OSX) prefixes
extern const std::string& shortkey_ctrl_prefix();
extern const std::string& shortkey_alt_prefix();
extern AppConfig* get_app_config();
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);

View File

@ -561,7 +561,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
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")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("&Preferences")) + dots + "\tCtrl+P", _(L("Application preferences")));
local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("&Preferences")) + dots +
#ifdef __APPLE__
"\tCtrl+,",
#else
"\tCtrl+P",
#endif
_(L("Application preferences")));
local_menu->AppendSeparator();
auto mode_menu = new wxMenu();
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("Simple")), _(L("Simple View Mode")));

View File

@ -88,13 +88,8 @@ KBShortcutsDialog::KBShortcutsDialog()
void KBShortcutsDialog::fill_shortcuts()
{
#ifdef __WXOSX__
const std::string ctrl = "";
const std::string alt = "";
#else
const std::string ctrl = "Ctrl+";
const std::string alt = "Alt+";
#endif // __WXOSX__
const std::string &ctrl = GUI::shortkey_ctrl_prefix();
const std::string &alt = GUI::shortkey_alt_prefix();
m_full_shortcuts.reserve(4);

View File

@ -324,7 +324,19 @@ void MainFrame::init_menubar()
if (m_plater != nullptr)
{
editMenu = new wxMenu();
wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + "\tCtrl+A", _(L("Selects all objects")),
// \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators,
// as the simple numeric accelerators spoil all numeric data entry.
wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) +
#ifdef _MSC_VER
"\t\xA0" + "Ctrl+\xA0" + "A"
#else
#ifdef __APPLE__
"\tCtrl+A"
#else
" - Ctrl+A"
#endif
#endif
, _(L("Selects all objects")),
[this](wxCommandEvent&) { m_plater->select_all(); }, "");
editMenu->AppendSeparator();
wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + "\tDel", _(L("Deletes the current selection")),
@ -386,19 +398,23 @@ void MainFrame::init_menubar()
// View menu
wxMenu* viewMenu = nullptr;
wxString sep =
#ifdef _MSC_VER
"\t";
#else
" - ";
#endif
if (m_plater) {
viewMenu = new wxMenu();
// \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators,
// as the simple numeric accelerators spoil all numeric data entry.
// The camera control accelerators are captured by GLCanvas3D::on_char().
wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("&Iso")) + "\t\xA0" + "0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); });
wxMenuItem* item_iso = append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + sep + "&0", _(L("Iso View")), [this](wxCommandEvent&) { select_view("iso"); });
viewMenu->AppendSeparator();
wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("&Top")) + "\t\xA0" + "1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); });
wxMenuItem* item_bottom = append_menu_item(viewMenu, wxID_ANY, _(L("&Bottom")) + "\t\xA0" + "2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); });
wxMenuItem* item_front = append_menu_item(viewMenu, wxID_ANY, _(L("&Front")) + "\t\xA0" + "3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); });
wxMenuItem* item_rear = append_menu_item(viewMenu, wxID_ANY, _(L("R&ear")) + "\t\xA0" + "4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); });
wxMenuItem* item_left = append_menu_item(viewMenu, wxID_ANY, _(L("&Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); });
wxMenuItem* item_right = append_menu_item(viewMenu, wxID_ANY, _(L("&Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); });
wxMenuItem* item_top = append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + sep + "&1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); });
wxMenuItem* item_bottom = append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + sep + "&2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); });
wxMenuItem* item_front = append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + sep + "&3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); });
wxMenuItem* item_rear = append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + sep + "&4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); });
wxMenuItem* item_left = append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + sep + "&5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); });
wxMenuItem* item_right = append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + sep + "&6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); });
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_iso->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_top->GetId());
@ -434,7 +450,7 @@ void MainFrame::init_menubar()
append_menu_item(helpMenu, wxID_ANY, _(L("&About Slic3r")), _(L("Show about dialog")),
[this](wxCommandEvent&) { Slic3r::GUI::about(); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _(L("&Keyboard Shortcuts")) + "\t\xA0?", _(L("Show the list of the keyboard shortcuts")),
append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")),
[this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
}

View File

@ -1028,7 +1028,8 @@ struct Plater::priv
unsigned int update_background_process(bool force_validation = false);
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
bool restart_background_process(unsigned int state);
void update_restart_background_process(bool force_scene_update, bool force_preview_update);
// returns bit mask of UpdateBackgroundProcessReturnState
unsigned int update_restart_background_process(bool force_scene_update, bool force_preview_update);
void export_gcode(fs::path output_path, PrintHostJob upload_job);
void reload_from_disk();
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
@ -1170,6 +1171,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this);
view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); });
view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); });
view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event<int> &evt)
{ if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); });
@ -1575,7 +1577,7 @@ std::unique_ptr<CheckboxFileDialog> Plater::priv::get_export_file(GUI::FileType
// Update printbility state of each of the ModelInstances.
this->update_print_volume_state();
// Find the file name of the first printable object.
fs::path output_file = this->model.propose_export_file_name();
fs::path output_file = this->model.propose_export_file_name_and_path();
switch (file_type) {
case FT_STL: output_file.replace_extension("stl"); break;
@ -1735,6 +1737,8 @@ void Plater::priv::arrange()
// Guard the arrange process
arranging.store(true);
wxBusyCursor wait;
// Disable the arrange button (to prevent reentrancies, we will call wxYied)
view3D->enable_toolbar_item("arrange", can_arrange());
@ -2045,7 +2049,7 @@ void Plater::priv::export_gcode(fs::path output_path, PrintHostJob upload_job)
this->restart_background_process(priv::UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT);
}
void Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview)
unsigned int Plater::priv::update_restart_background_process(bool force_update_scene, bool force_update_preview)
{
// bitmask of UpdateBackgroundProcessReturnState
unsigned int state = this->update_background_process(false);
@ -2055,6 +2059,7 @@ void Plater::priv::update_restart_background_process(bool force_update_scene, bo
if (force_update_preview)
this->preview->reload_print();
this->restart_background_process(state);
return state;
}
void Plater::priv::update_fff_scene()
@ -2108,31 +2113,10 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
{
if (obj_idx < 0)
return;
const auto model_object = model.objects[obj_idx];
Model model_fixed;// = new Model();
fix_model_by_win10_sdk_gui(*model_object, this->fff_print, model_fixed);
auto new_obj_idxs = load_model_objects(model_fixed.objects);
if (new_obj_idxs.empty())
return;
for(auto new_obj_idx : new_obj_idxs) {
auto o = model.objects[new_obj_idx];
o->clear_instances();
for (auto instance: model_object->instances)
o->add_instance(*instance);
o->invalidate_bounding_box();
if (o->volumes.size() == model_object->volumes.size()) {
for (int i = 0; i < o->volumes.size(); i++) {
o->volumes[i]->config.apply(model_object->volumes[i]->config);
}
}
// FIXME restore volumes and their configs, layer_height_ranges, layer_height_profile
}
remove(obj_idx);
fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
this->object_list_changed();
this->update();
this->schedule_background_process();
}
void Plater::priv::set_current_panel(wxPanel* panel)
@ -2553,7 +2537,7 @@ void Plater::priv::init_view_toolbar()
GLToolbarItem::Data item;
item.name = "3D";
item.tooltip = GUI::L_str("3D editor view [Ctrl+5]");
item.tooltip = GUI::L_str("3D editor view") + " [" + GUI::shortkey_ctrl_prefix() + "5]";
item.sprite_id = 0;
item.action_event = EVT_GLVIEWTOOLBAR_3D;
item.is_toggable = false;
@ -2561,7 +2545,7 @@ void Plater::priv::init_view_toolbar()
return;
item.name = "Preview";
item.tooltip = GUI::L_str("Preview [Ctrl+6]");
item.tooltip = GUI::L_str("Preview") + " [" + GUI::shortkey_ctrl_prefix() + "6]";
item.sprite_id = 1;
item.action_event = EVT_GLVIEWTOOLBAR_PREVIEW;
item.is_toggable = false;
@ -2846,51 +2830,43 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
p->load_model_objects(new_objects);
}
void Plater::export_gcode(fs::path output_path)
void Plater::export_gcode()
{
if (p->model.objects.empty())
return;
// select output file
if (output_path.empty()) {
// XXX: take output path from CLI opts? Ancient Slic3r versions used to do that...
// If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares.
fs::path default_output_file;
try {
default_output_file = this->p->background_process.current_print()->output_filepath(output_path.string());
} catch (const std::exception &ex) {
show_error(this, ex.what());
return;
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string());
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip file as:")),
start_dir,
from_path(default_output_file.filename()),
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
if (dlg.ShowModal() == wxID_OK) {
fs::path path = into_path(dlg.GetPath());
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
output_path = std::move(path);
}
} else {
try {
output_path = this->p->background_process.current_print()->output_filepath(output_path.string());
} catch (const std::exception &ex) {
show_error(this, ex.what());
return;
}
// If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares.
fs::path default_output_file;
try {
// Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
// Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
unsigned int state = this->p->update_restart_background_process(false, false);
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
return;
default_output_file = this->p->background_process.current_print()->output_filepath("");
} catch (const std::exception &ex) {
show_error(this, ex.what());
return;
}
default_output_file = fs::path(Slic3r::fold_utf8_to_ascii(default_output_file.string()));
auto start_dir = wxGetApp().app_config->get_last_output_dir(default_output_file.parent_path().string());
if (! output_path.empty()) {
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _(L("Save G-code file as:")) : _(L("Save Zip 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()),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
fs::path output_path;
if (dlg.ShowModal() == wxID_OK) {
fs::path path = into_path(dlg.GetPath());
wxGetApp().app_config->update_last_output_dir(path.parent_path().string());
output_path = std::move(path);
}
if (! output_path.empty())
p->export_gcode(std::move(output_path), PrintHostJob());
}
}
void Plater::export_stl(bool selection_only)
@ -2991,7 +2967,12 @@ void Plater::send_gcode()
// Obtain default output path
fs::path default_output_file;
try {
default_output_file = this->p->background_process.current_print()->output_filepath("");
// Update the background processing, so that the placeholder parser will get the correct values for the ouput file template.
// Also if there is something wrong with the current configuration, a pop-up dialog will be shown and the export will not be performed.
unsigned int state = this->p->update_restart_background_process(false, false);
if (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID)
return;
default_output_file = this->p->background_process.current_print()->output_filepath("");
} catch (const std::exception &ex) {
show_error(this, ex.what());
return;

View File

@ -140,8 +140,7 @@ public:
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false);
// Note: empty path means "use the default"
void export_gcode(boost::filesystem::path output_path = boost::filesystem::path());
void export_gcode();
void export_stl(bool selection_only = false);
void export_amf();
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());

View File

@ -517,16 +517,6 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
++ m_num_default_presets;
}
bool is_file_plain(const std::string &path)
{
#ifdef _MSC_VER
DWORD attributes = GetFileAttributesW(boost::nowide::widen(path).c_str());
return (attributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) == 0;
#else
return true;
#endif
}
// Load all presets found in dir_path.
// Throws an exception on error.
void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir)
@ -538,10 +528,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri
// (see the "Preset already present, not loading" message).
std::deque<Preset> presets_loaded;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini") &&
// Ignore system and hidden files, which may be created by the DropBox synchronisation process.
// https://github.com/prusa3d/Slic3r/issues/1298
is_file_plain(dir_entry.path().string())) {
if (Slic3r::is_ini_file(dir_entry)) {
std::string name = dir_entry.path().filename().string();
// Remove the .ini suffix.
name.erase(name.size() - 4);
@ -1169,6 +1156,18 @@ std::string PresetCollection::name() const
}
}
std::string PresetCollection::section_name() const
{
switch (this->type()) {
case Preset::TYPE_PRINT: return "print";
case Preset::TYPE_FILAMENT: return "filament";
case Preset::TYPE_SLA_PRINT: return "sla_print";
case Preset::TYPE_SLA_MATERIAL: return "sla_material";
case Preset::TYPE_PRINTER: return "printer";
default: return "invalid";
}
}
std::vector<std::string> PresetCollection::system_preset_names() const
{
size_t num = 0;

View File

@ -230,7 +230,10 @@ public:
void reset(bool delete_files);
Preset::Type type() const { return m_type; }
// Name, to be used on the screen and in error messages. Not localized.
std::string name() const;
// Name, to be used as a section name in config bundle, and as a folder name for presets.
std::string section_name() const;
const std::deque<Preset>& operator()() const { return m_presets; }
// Add default preset at the start of the collection, increment the m_default_preset counter.

View File

@ -243,7 +243,7 @@ std::string PresetBundle::load_system_presets()
std::string errors_cummulative;
bool first = true;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) {
if (Slic3r::is_ini_file(dir_entry)) {
std::string name = dir_entry.path().filename().string();
// Remove the .ini suffix.
name.erase(name.size() - 4);
@ -1208,7 +1208,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla
#else
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
#endif
/ presets->name() / file_name).make_preferred();
/ presets->section_name() / file_name).make_preferred();
// Load the preset into the list of presets, save it to disk.
Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false);
if (flags & LOAD_CFGBNDLE_SAVE)
@ -1365,7 +1365,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
if (preset.is_default || preset.is_external || (preset.is_system && ! export_system_settings))
// Only export the common presets, not external files or the default preset.
continue;
c << std::endl << "[" << presets->name() << ":" << preset.name << "]" << std::endl;
c << std::endl << "[" << presets->section_name() << ":" << preset.name << "]" << std::endl;
for (const std::string &opt_key : preset.config.keys())
c << opt_key << " = " << preset.config.serialize(opt_key) << std::endl;
}

View File

@ -216,7 +216,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path
HRESULT hr = (*s_RoInitialize)(RO_INIT_MULTITHREADED);
{
on_progress(L("Exporting the source model"), 20);
on_progress(L("Exporting source model"), 20);
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> fileStream;
hr = winrt_open_file_stream(boost::nowide::widen(path_src), ABI::Windows::Storage::FileAccessMode::FileAccessMode_Read, fileStream.GetAddressOf(), throw_on_cancel);
@ -239,7 +239,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path
unsigned num_meshes = 0;
hr = meshes->get_Size(&num_meshes);
on_progress(L("Repairing the model by the Netfabb service"), 40);
on_progress(L("Repairing model by the Netfabb service"), 40);
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncAction> repairAsync;
hr = model->RepairAsync(repairAsync.GetAddressOf());
@ -248,7 +248,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path
throw std::runtime_error(L("Mesh repair failed."));
repairAsync->GetResults();
on_progress(L("Loading the repaired model"), 60);
on_progress(L("Loading repaired model"), 60);
// Verify the number of meshes returned after the repair action.
meshes.Reset();
@ -316,7 +316,7 @@ public:
const char* what() const throw() { return "Model repair has been canceled"; }
};
void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result)
void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
{
std::mutex mutex;
std::condition_variable condition;
@ -329,6 +329,12 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr
std::atomic<bool> canceled = false;
std::atomic<bool> finished = false;
std::vector<ModelVolume*> volumes;
if (volume_idx == -1)
volumes = model_object.volumes;
else
volumes.emplace_back(model_object.volumes[volume_idx]);
// Open a progress dialog.
wxProgressDialog progress_dialog(
_(L("Model fixing")),
@ -336,43 +342,64 @@ void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &pr
100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
// 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;
auto on_progress = [&mutex, &condition, &progress](const char *msg, unsigned prcnt) {
bool success = false;
size_t ivolume = 0;
auto on_progress = [&mutex, &condition, &ivolume, &volumes, &progress](const char *msg, unsigned prcnt) {
std::lock_guard<std::mutex> lk(mutex);
progress.message = msg;
progress.percent = prcnt;
progress.percent = (int)floor((float(prcnt) + float(ivolume) * 100.f) / float(volumes.size()));
progress.updated = true;
condition.notify_all();
};
auto worker_thread = boost::thread([&model_object, &print, &result, on_progress, &success, &canceled, &finished]() {
auto worker_thread = boost::thread([&model_object, &volumes, &ivolume, on_progress, &success, &canceled, &finished]() {
try {
on_progress(L("Exporting the source model"), 0);
boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
path_src += ".3mf";
Model model;
DynamicPrintConfig config;
model.add_object(model_object);
if (! Slic3r::store_3mf(path_src.string().c_str(), &model, &config)) {
std::vector<TriangleMesh> meshes_repaired;
meshes_repaired.reserve(volumes.size());
for (; ivolume < volumes.size(); ++ ivolume) {
on_progress(L("Exporting source model"), 0);
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();
if (! Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr)) {
boost::filesystem::remove(path_src);
throw std::runtime_error(L("Export of a temporary 3mf file failed"));
}
model.clear_objects();
model.clear_materials();
boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
path_dst += ".3mf";
fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress,
[&canceled]() { if (canceled) throw RepairCanceledException(); });
boost::filesystem::remove(path_src);
throw std::runtime_error(L("Export of a temporary 3mf file failed"));
// PresetBundle bundle;
on_progress(L("Loading repaired model"), 80);
DynamicPrintConfig config;
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model);
boost::filesystem::remove(path_dst);
if (! loaded)
throw std::runtime_error(L("Import of the repaired 3mf file failed"));
if (model.objects.size() == 0)
throw std::runtime_error(L("Repaired 3MF file does not contain any object"));
if (model.objects.size() > 1)
throw std::runtime_error(L("Repaired 3MF file contains more than one object"));
if (model.objects.front()->volumes.size() == 0)
throw std::runtime_error(L("Repaired 3MF file does not contain any volume"));
if (model.objects.front()->volumes.size() > 1)
throw std::runtime_error(L("Repaired 3MF file contains more than one volume"));
meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh));
}
model.clear_objects();
model.clear_materials();
boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
path_dst += ".3mf";
fix_model_by_win10_sdk(path_src.string().c_str(), path_dst.string(), on_progress,
[&canceled]() { if (canceled) throw RepairCanceledException(); });
boost::filesystem::remove(path_src);
// PresetBundle bundle;
on_progress(L("Loading the repaired model"), 80);
bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &result);
result.objects[0]->name = boost::filesystem::path(model_object.name).filename().stem().string() + "_fixed";
boost::filesystem::remove(path_dst);
if (! loaded)
throw std::runtime_error(L("Import of the repaired 3mf file failed"));
for (size_t i = 0; i < volumes.size(); ++ i) {
volumes[i]->mesh = std::move(meshes_repaired[i]);
volumes[i]->set_new_unique_id();
}
model_object.invalidate_bounding_box();
-- ivolume;
on_progress(L("Model repair finished"), 100);
success = true;
finished = true;
on_progress(L("Model repair finished"), 100);
} catch (RepairCanceledException & /* ex */) {
canceled = true;
finished = true;

View File

@ -12,12 +12,12 @@ class Print;
#ifdef HAS_WIN10SDK
extern bool is_windows10();
extern void fix_model_by_win10_sdk_gui(const ModelObject &model_object, const Print &print, Model &result);
extern void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx);
#else /* HAS_WIN10SDK */
inline bool is_windows10() { return false; }
inline void fix_model_by_win10_sdk_gui(const ModelObject &, const Print &, Model &) {}
inline void fix_model_by_win10_sdk_gui(ModelObject &, int) {}
#endif /* HAS_WIN10SDK */

View File

@ -180,12 +180,11 @@ bool PresetUpdater::priv::get_file(const std::string &url, const fs::path &targe
// Remove leftover paritally downloaded files, if any.
void PresetUpdater::priv::prune_tmps() const
{
for (fs::directory_iterator it(cache_path); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == TMP_EXTENSION) {
BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << it->path().string();
fs::remove(it->path());
for (auto &dir_entry : boost::filesystem::directory_iterator(cache_path))
if (is_plain_file(dir_entry) && dir_entry.path().extension() == TMP_EXTENSION) {
BOOST_LOG_TRIVIAL(debug) << "Cache prune: " << dir_entry.path().string();
fs::remove(dir_entry.path());
}
}
}
// Get Slic3rPE version available online, save in AppConfig.
@ -299,9 +298,9 @@ void PresetUpdater::priv::check_install_indices() const
{
BOOST_LOG_TRIVIAL(info) << "Checking if indices need to be installed from resources...";
for (fs::directory_iterator it(rsrc_path); it != fs::directory_iterator(); ++it) {
const auto &path = it->path();
if (path.extension() == ".idx") {
for (auto &dir_entry : boost::filesystem::directory_iterator(rsrc_path))
if (is_idx_file(dir_entry)) {
const auto &path = dir_entry.path();
const auto path_in_cache = cache_path / path.filename();
if (! fs::exists(path_in_cache)) {
@ -318,7 +317,6 @@ void PresetUpdater::priv::check_install_indices() const
}
}
}
}
}
// Generates a list of bundle updates that are to be performed