Merge branch 'master' into lm_sla_supports_auto2
This commit is contained in:
commit
d32d0a7636
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 */
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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")));
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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(); });
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user