Fixed conflicts after merge with dev branch

This commit is contained in:
enricoturri1966 2022-01-12 15:54:48 +01:00
commit a2dc1cd446
147 changed files with 435771 additions and 1275 deletions

View file

@ -175,6 +175,9 @@ void AppConfig::set_defaults()
if (get("show_splash_screen").empty())
set("show_splash_screen", "1");
if (get("restore_win_position").empty())
set("restore_win_position", "1"); // allowed values - "1", "0", "crashed_at_..."
if (get("show_hints").empty())
set("show_hints", "1");

View file

@ -100,7 +100,7 @@ public:
bool has_section(const std::string &section) const
{ return m_storage.find(section) != m_storage.end(); }
const std::map<std::string, std::string>& get_section(const std::string &section) const
{ return m_storage.find(section)->second; }
{ auto it = m_storage.find(section); assert(it != m_storage.end()); return it->second; }
void set_section(const std::string &section, const std::map<std::string, std::string>& data)
{ m_storage[section] = data; }
void clear_section(const std::string &section)

View file

@ -758,6 +758,8 @@ public:
ConfigOptionIntsTempl() : ConfigOptionVector<int>() {}
explicit ConfigOptionIntsTempl(size_t n, int value) : ConfigOptionVector<int>(n, value) {}
explicit ConfigOptionIntsTempl(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {}
explicit ConfigOptionIntsTempl(const std::vector<int> &v) : ConfigOptionVector<int>(v) {}
explicit ConfigOptionIntsTempl(std::vector<int> &&v) : ConfigOptionVector<int>(std::move(v)) {}
static ConfigOptionType static_type() { return coInts; }
ConfigOptionType type() const override { return static_type(); }

View file

@ -677,7 +677,7 @@ namespace DoExport {
print_statistics.total_weight = total_weight;
print_statistics.total_cost = total_cost;
print_statistics.filament_stats = result.print_statistics.volumes_per_extruder;
print_statistics.filament_stats = result.print_statistics.volumes_per_extruder;
}
// if any reserved keyword is found, returns a std::vector containing the first MAX_COUNT keywords found
@ -984,19 +984,26 @@ namespace DoExport {
static std::string update_print_stats_and_format_filament_stats(
const bool has_wipe_tower,
const WipeTowerData &wipe_tower_data,
const FullPrintConfig &config,
const std::vector<Extruder> &extruders,
unsigned int initial_extruder_id,
PrintStatistics &print_statistics)
{
std::string filament_stats_string_out;
print_statistics.clear();
print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges);
print_statistics.initial_extruder_id = initial_extruder_id;
std::vector<std::string> filament_types;
if (! extruders.empty()) {
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_cm3("; filament used [cm3] = ", 0);
std::pair<std::string, unsigned int> out_filament_used_g ("; filament used [g] = ", 0);
std::pair<std::string, unsigned int> out_filament_cost ("; filament cost = ", 0);
for (const Extruder &extruder : extruders) {
print_statistics.printing_extruders.emplace_back(extruder.id());
filament_types.emplace_back(config.filament_type.get_at(extruder.id()));
double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f);
double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter
double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
@ -1036,6 +1043,13 @@ namespace DoExport {
filament_stats_string_out += "\n" + out_filament_used_g.first;
if (out_filament_cost.second)
filament_stats_string_out += "\n" + out_filament_cost.first;
print_statistics.initial_filament_type = config.filament_type.get_at(initial_extruder_id);
std::sort(filament_types.begin(), filament_types.end());
print_statistics.printing_filament_types = filament_types.front();
for (size_t i = 1; i < filament_types.size(); ++ i) {
print_statistics.printing_filament_types += ",";
print_statistics.printing_filament_types += filament_types[i];
}
}
return filament_stats_string_out;
}
@ -1486,7 +1500,9 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
file.write(DoExport::update_print_stats_and_format_filament_stats(
// Const inputs
has_wipe_tower, print.wipe_tower_data(),
this->config(),
m_writer.extruders(),
initial_extruder_id,
// Modifies
print.m_print_statistics));
file.write("\n");
@ -1685,40 +1701,56 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc
// Do not process this piece of G-code by the time estimator, it already knows the values through another sources.
void GCode::print_machine_envelope(GCodeOutputStream &file, Print &print)
{
if ((print.config().gcode_flavor.value == gcfMarlinLegacy || print.config().gcode_flavor.value == gcfMarlinFirmware)
const GCodeFlavor flavor = print.config().gcode_flavor.value;
if ( (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware)
&& print.config().machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) {
int factor = flavor == gcfRepRapFirmware ? 60 : 1; // RRF M203 and M566 are in mm/min
file.write_format("M201 X%d Y%d Z%d E%d ; sets maximum accelerations, mm/sec^2\n",
int(print.config().machine_max_acceleration_x.values.front() + 0.5),
int(print.config().machine_max_acceleration_y.values.front() + 0.5),
int(print.config().machine_max_acceleration_z.values.front() + 0.5),
int(print.config().machine_max_acceleration_e.values.front() + 0.5));
file.write_format("M203 X%d Y%d Z%d E%d ; sets maximum feedrates, mm/sec\n",
int(print.config().machine_max_feedrate_x.values.front() + 0.5),
int(print.config().machine_max_feedrate_y.values.front() + 0.5),
int(print.config().machine_max_feedrate_z.values.front() + 0.5),
int(print.config().machine_max_feedrate_e.values.front() + 0.5));
file.write_format("M203 X%d Y%d Z%d E%d ; sets maximum feedrates, %s\n",
int(print.config().machine_max_feedrate_x.values.front() * factor + 0.5),
int(print.config().machine_max_feedrate_y.values.front() * factor + 0.5),
int(print.config().machine_max_feedrate_z.values.front() * factor + 0.5),
int(print.config().machine_max_feedrate_e.values.front() * factor + 0.5),
factor == 60 ? "mm / min" : "mm / sec");
// Now M204 - acceleration. This one is quite hairy thanks to how Marlin guys care about
// backwards compatibility: https://github.com/prusa3d/PrusaSlicer/issues/1089
// Legacy Marlin should export travel acceleration the same as printing acceleration.
// MarlinFirmware has the two separated.
int travel_acc = print.config().gcode_flavor == gcfMarlinLegacy
int travel_acc = flavor == gcfMarlinLegacy
? int(print.config().machine_max_acceleration_extruding.values.front() + 0.5)
: int(print.config().machine_max_acceleration_travel.values.front() + 0.5);
file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
int(print.config().machine_max_acceleration_retracting.values.front() + 0.5),
travel_acc);
// Retract acceleration not accepted in M204 in RRF
if (flavor == gcfRepRapFirmware)
file.write_format("M204 P%d T%d ; sets acceleration (P, T), mm/sec^2\n",
int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
travel_acc);
else
file.write_format("M204 P%d R%d T%d ; sets acceleration (P, T) and retract acceleration (R), mm/sec^2\n",
int(print.config().machine_max_acceleration_extruding.values.front() + 0.5),
int(print.config().machine_max_acceleration_retracting.values.front() + 0.5),
travel_acc);
assert(is_decimal_separator_point());
file.write_format("M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
print.config().machine_max_jerk_x.values.front(),
print.config().machine_max_jerk_y.values.front(),
print.config().machine_max_jerk_z.values.front(),
print.config().machine_max_jerk_e.values.front());
file.write_format("M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n",
int(print.config().machine_min_extruding_rate.values.front() + 0.5),
int(print.config().machine_min_travel_rate.values.front() + 0.5));
file.write_format(flavor == gcfRepRapFirmware
? "M566 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/min\n"
: "M205 X%.2lf Y%.2lf Z%.2lf E%.2lf ; sets the jerk limits, mm/sec\n",
print.config().machine_max_jerk_x.values.front() * factor,
print.config().machine_max_jerk_y.values.front() * factor,
print.config().machine_max_jerk_z.values.front() * factor,
print.config().machine_max_jerk_e.values.front() * factor);
if (flavor != gcfRepRapFirmware)
file.write_format("M205 S%d T%d ; sets the minimum extruding and travel feed rate, mm/sec\n",
int(print.config().machine_min_extruding_rate.values.front() + 0.5),
int(print.config().machine_min_travel_rate.values.front() + 0.5));
else {
// M205 Sn Tn not supported in RRF. They use M203 Inn to set minimum feedrate for
// all moves. This is currently not implemented.
}
}
}

View file

@ -811,13 +811,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
// Skip the non-whitespaces of the F parameter up the comment or end of line.
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos);
// Append the rest of the line without the comment.
if (fpos < end)
// The G-code line is not empty yet. Emit the rest of it.
new_gcode.append(fpos, end - fpos);
else if (remove && new_gcode == "G1") {
if (remove && (fpos == end || *fpos == '\n') && (new_gcode == "G1" || boost::ends_with(new_gcode, "\nG1"))) {
// The G-code line only contained the F word, now it is empty. Remove it completely including the comments.
new_gcode.resize(new_gcode.size() - 2);
end = line_end;
} else {
// The G-code line may not be empty yet. Emit the rest of it.
new_gcode.append(fpos, end - fpos);
}
}
// Process the rest of the line.
@ -845,6 +845,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (pos < gcode_end)
new_gcode.append(pos, gcode_end - pos);
// There should be no empty G1 lines emitted.
assert(new_gcode.find("G1\n") == std::string::npos);
return new_gcode;
}

View file

@ -726,6 +726,9 @@ void GCodeProcessorResult::reset() {
filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
#if ENABLE_SPIRAL_VASE_LAYERS
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
#endif // ENABLE_SPIRAL_VASE_LAYERS
time = 0;
}
#else
@ -741,6 +744,9 @@ void GCodeProcessorResult::reset() {
filament_diameters = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER);
filament_densities = std::vector<float>(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY);
custom_gcode_per_print_z = std::vector<CustomGCode::Item>();
#if ENABLE_SPIRAL_VASE_LAYERS
spiral_vase_layers = std::vector<std::pair<float, std::pair<size_t, size_t>>>();
#endif // ENABLE_SPIRAL_VASE_LAYERS
}
#endif // ENABLE_GCODE_VIEWER_STATISTICS
@ -842,12 +848,17 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
}
if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) {
m_time_processor.machine_limits = reinterpret_cast<const MachineEnvelopeConfig&>(config);
if (m_flavor == gcfMarlinLegacy) {
// Legacy Marlin does not have separate travel acceleration, it uses the 'extruding' value instead.
m_time_processor.machine_limits.machine_max_acceleration_travel = m_time_processor.machine_limits.machine_max_acceleration_extruding;
}
if (m_flavor == gcfRepRapFirmware) {
// RRF does not support setting min feedrates. Set them to zero.
m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.);
m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.);
}
}
// Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful.
@ -882,6 +893,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
m_first_layer_height = std::abs(first_layer_height->value);
m_result.max_print_height = config.max_print_height;
#if ENABLE_SPIRAL_VASE_LAYERS
const ConfigOptionBool* spiral_vase = config.option<ConfigOptionBool>("spiral_vase");
if (spiral_vase != nullptr)
m_spiral_vase_active = spiral_vase->value;
#endif // ENABLE_SPIRAL_VASE_LAYERS
}
void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
@ -1009,7 +1026,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
if (machine_limits_usage != nullptr)
use_machine_limits = machine_limits_usage->value != MachineLimitsUsage::Ignore;
if (use_machine_limits && (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware)) {
if (use_machine_limits && (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfRepRapFirmware)) {
const ConfigOptionFloats* machine_max_acceleration_x = config.option<ConfigOptionFloats>("machine_max_acceleration_x");
if (machine_max_acceleration_x != nullptr)
m_time_processor.machine_limits.machine_max_acceleration_x.values = machine_max_acceleration_x->values;
@ -1076,12 +1093,22 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloats* machine_min_extruding_rate = config.option<ConfigOptionFloats>("machine_min_extruding_rate");
if (machine_min_extruding_rate != nullptr)
if (machine_min_extruding_rate != nullptr) {
m_time_processor.machine_limits.machine_min_extruding_rate.values = machine_min_extruding_rate->values;
if (m_flavor == gcfRepRapFirmware) {
// RRF does not support setting min feedrates. Set zero.
m_time_processor.machine_limits.machine_min_extruding_rate.values.assign(m_time_processor.machine_limits.machine_min_extruding_rate.size(), 0.);
}
}
const ConfigOptionFloats* machine_min_travel_rate = config.option<ConfigOptionFloats>("machine_min_travel_rate");
if (machine_min_travel_rate != nullptr)
if (machine_min_travel_rate != nullptr) {
m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values;
if (m_flavor == gcfRepRapFirmware) {
// RRF does not support setting min feedrates. Set zero.
m_time_processor.machine_limits.machine_min_travel_rate.values.assign(m_time_processor.machine_limits.machine_min_travel_rate.size(), 0.);
}
}
}
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
@ -1115,6 +1142,12 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config)
const ConfigOptionFloat* max_print_height = config.option<ConfigOptionFloat>("max_print_height");
if (max_print_height != nullptr)
m_result.max_print_height = max_print_height->value;
#if ENABLE_SPIRAL_VASE_LAYERS
const ConfigOptionBool* spiral_vase = config.option<ConfigOptionBool>("spiral_vase");
if (spiral_vase != nullptr)
m_spiral_vase_active = spiral_vase->value;
#endif // ENABLE_SPIRAL_VASE_LAYERS
}
void GCodeProcessor::enable_stealth_time_estimator(bool enabled)
@ -1177,6 +1210,10 @@ void GCodeProcessor::reset()
m_options_z_corrector.reset();
#if ENABLE_SPIRAL_VASE_LAYERS
m_spiral_vase_active = false;
#endif // ENABLE_SPIRAL_VASE_LAYERS
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
m_mm3_per_mm_compare.reset();
m_height_compare.reset();
@ -1865,6 +1902,16 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers
// layer change tag
if (comment == reserved_tag(ETags::Layer_Change)) {
++m_layer_id;
#if ENABLE_SPIRAL_VASE_LAYERS
if (m_spiral_vase_active) {
assert(!m_result.moves.empty());
size_t move_id = m_result.moves.size() - 1;
if (!m_result.spiral_vase_layers.empty() && m_end_position[Z] == m_result.spiral_vase_layers.back().first)
m_result.spiral_vase_layers.back().second.second = move_id;
else
m_result.spiral_vase_layers.push_back({ m_end_position[Z], { move_id, move_id } });
}
#endif // ENABLE_SPIRAL_VASE_LAYERS
return;
}
@ -2670,6 +2717,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]);
}
#if ENABLE_SPIRAL_VASE_LAYERS
if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty() && !m_result.moves.empty())
m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1;
#endif // ENABLE_SPIRAL_VASE_LAYERS
// store move
store_move_vertex(type);
}

View file

@ -125,6 +125,9 @@ namespace Slic3r {
std::vector<float> filament_densities;
PrintEstimatedStatistics print_statistics;
std::vector<CustomGCode::Item> custom_gcode_per_print_z;
#if ENABLE_SPIRAL_VASE_LAYERS
std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers;
#endif // ENABLE_SPIRAL_VASE_LAYERS
#if ENABLE_GCODE_VIEWER_STATISTICS
int64_t time{ 0 };
@ -532,6 +535,9 @@ namespace Slic3r {
SeamsDetector m_seams_detector;
OptionsZCorrector m_options_z_corrector;
size_t m_last_default_color_id;
#if ENABLE_SPIRAL_VASE_LAYERS
bool m_spiral_vase_active;
#endif // ENABLE_SPIRAL_VASE_LAYERS
#if ENABLE_GCODE_VIEWER_STATISTICS
std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
#endif // ENABLE_GCODE_VIEWER_STATISTICS

View file

@ -20,8 +20,10 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config)
this->config.apply(print_config, true);
m_extrusion_axis = get_extrusion_axis(this->config);
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
bool is_marlin = print_config.gcode_flavor.value == gcfMarlinLegacy || print_config.gcode_flavor.value == gcfMarlinFirmware;
m_max_acceleration = std::lrint((is_marlin && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ?
bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy
|| print_config.gcode_flavor.value == gcfMarlinFirmware
|| print_config.gcode_flavor.value == gcfRepRapFirmware;
m_max_acceleration = std::lrint((use_mach_limits && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ?
print_config.machine_max_acceleration_extruding.values.front() : 0);
}

View file

@ -1797,19 +1797,28 @@ std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(con
line_end_f = facet[1] + t2 * (facet[2] - facet[1]);
}
Point line_start(scale_(line_start_f.x()), scale_(line_start_f.y()));
Point line_end(scale_(line_end_f.x()), scale_(line_end_f.y()));
line_start -= print_object.center_offset();
line_end -= print_object.center_offset();
Line line_to_test(Point(scale_(line_start_f.x()), scale_(line_start_f.y())),
Point(scale_(line_end_f.x()), scale_(line_end_f.y())));
line_to_test.translate(-print_object.center_offset());
// BoundingBoxes for EdgeGrids are computed from printable regions. It is possible that the painted line (line_to_test) could
// be outside EdgeGrid's BoundingBox, for example, when the negative volume is used on the painted area (GH #7618).
// To ensure that the painted line is always inside EdgeGrid's BoundingBox, it is clipped by EdgeGrid's BoundingBox in cases
// when any of the endpoints of the line are outside the EdgeGrid's BoundingBox.
if (const BoundingBox &edge_grid_bbox = edge_grids[layer_idx].bbox(); !edge_grid_bbox.contains(line_to_test.a) || !edge_grid_bbox.contains(line_to_test.b)) {
// If the painted line (line_to_test) is entirely outside EdgeGrid's BoundingBox, skip this painted line.
if (!edge_grid_bbox.overlap(BoundingBox(Points{line_to_test.a, line_to_test.b})) ||
!line_to_test.clip_with_bbox(edge_grid_bbox))
continue;
}
size_t mutex_idx = layer_idx & 0x3F;
assert(mutex_idx < painted_lines_mutex.size());
PaintedLineVisitor visitor(edge_grids[layer_idx], painted_lines[layer_idx], painted_lines_mutex[mutex_idx], 16);
visitor.line_to_test.a = line_start;
visitor.line_to_test.b = line_end;
visitor.color = int(extruder_idx);
edge_grids[layer_idx].visit_cells_intersecting_line(line_start, line_end, visitor);
visitor.line_to_test = line_to_test;
visitor.color = int(extruder_idx);
edge_grids[layer_idx].visit_cells_intersecting_line(line_to_test.a, line_to_test.b, visitor);
}
}
}); // end of parallel_for

View file

@ -1248,6 +1248,13 @@ DynamicConfig PrintStatistics::config() const
config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight));
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat(this->total_wipe_tower_cost));
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat(this->total_wipe_tower_filament));
config.set_key_value("initial_tool", new ConfigOptionInt(int(this->initial_extruder_id)));
config.set_key_value("initial_extruder", new ConfigOptionInt(int(this->initial_extruder_id)));
config.set_key_value("initial_filament_type", new ConfigOptionString(this->initial_filament_type));
config.set_key_value("printing_filament_types", new ConfigOptionString(this->printing_filament_types));
config.set_key_value("num_printing_extruders", new ConfigOptionInt(int(this->printing_extruders.size())));
// config.set_key_value("printing_extruders", new ConfigOptionInts(std::vector<int>(this->printing_extruders.begin(), this->printing_extruders.end())));
return config;
}
@ -1257,7 +1264,8 @@ DynamicConfig PrintStatistics::placeholders()
for (const std::string &key : {
"print_time", "normal_print_time", "silent_print_time",
"used_filament", "extruded_volume", "total_cost", "total_weight",
"total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"})
"total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament",
"initial_tool", "initial_extruder", "initial_filament_type", "printing_filament_types", "num_printing_extruders" })
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
return config;
}

View file

@ -455,6 +455,10 @@ struct PrintStatistics
double total_weight;
double total_wipe_tower_cost;
double total_wipe_tower_filament;
std::vector<unsigned int> printing_extruders;
unsigned int initial_extruder_id;
std::string initial_filament_type;
std::string printing_filament_types;
std::map<size_t, double> filament_stats;
// Config with the filled in print statistics.
@ -472,7 +476,11 @@ struct PrintStatistics
total_weight = 0.;
total_wipe_tower_cost = 0.;
total_wipe_tower_filament = 0.;
initial_extruder_id = 0;
initial_filament_type.clear();
printing_filament_types.clear();
filament_stats.clear();
printing_extruders.clear();
}
};

View file

@ -1614,7 +1614,7 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Use for time estimate"));
def->enum_labels.push_back(L("Ignore"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<MachineLimitsUsage>(MachineLimitsUsage::EmitToGCode));
def->set_default_value(new ConfigOptionEnum<MachineLimitsUsage>(MachineLimitsUsage::TimeEstimateOnly));
{
struct AxisDefault {

View file

@ -3,7 +3,6 @@
#include <libslic3r/SLA/RasterBase.hpp>
#include "libslic3r/ExPolygon.hpp"
#include "libslic3r/MTUtils.hpp"
// For rasterizing
#include <agg/agg_basics.h>

View file

@ -47,7 +47,7 @@ inline indexed_triangle_set straight_walls(const Polygon &plate,
double lo_z,
double hi_z)
{
return walls(plate, plate, lo_z, hi_z);
return wall_strip(plate, hi_z, lo_z); //walls(plate, plate, lo_z, hi_z);
}
// Function to cut tiny connector cavities for a given polygon. The input poly

View file

@ -77,6 +77,8 @@ std::unique_ptr<RasterBase> create_raster_grayscale_aa(
if (gamma > 0)
rst = std::make_unique<RasterGrayscaleAAGammaPower>(res, pxdim, tr, gamma);
else if (std::abs(gamma - 1.) < 1e-6)
rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_none());
else
rst = std::make_unique<RasterGrayscaleAA>(res, pxdim, tr, agg::gamma_threshold(.5));

View file

@ -9,7 +9,7 @@
#include <cstdint>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/SLA/Concurrency.hpp>
//#include <libslic3r/SLA/Concurrency.hpp>
namespace Slic3r {

View file

@ -10,6 +10,8 @@
#include "MTUtils.hpp"
#include "Zipper.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
namespace Slic3r {
enum SLAPrintStep : unsigned int {

View file

@ -2,7 +2,6 @@
#include "SlicesToTriangleMesh.hpp"
//#include "libslic3r/MTUtils.hpp"
#include "libslic3r/Execution/ExecutionTBB.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Tesselate.hpp"
@ -12,39 +11,6 @@
namespace Slic3r {
inline indexed_triangle_set wall_strip(const Polygon &poly,
double lower_z_mm,
double upper_z_mm)
{
indexed_triangle_set ret;
size_t startidx = ret.vertices.size();
size_t offs = poly.points.size();
ret.vertices.reserve(ret.vertices.size() + 2 *offs);
// The expression unscaled(p).cast<float>().eval() is important here
// as it ensures identical conversion of 2D scaled coordinates to float 3D
// to that used by the tesselation. This way, the duplicated vertices in the
// output mesh can be found with the == operator of the points.
// its_merge_vertices will then reliably remove the duplicates.
for (const Point &p : poly.points)
ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(lower_z_mm)));
for (const Point &p : poly.points)
ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(upper_z_mm)));
for (size_t i = startidx + 1; i < startidx + offs; ++i) {
ret.indices.emplace_back(i - 1, i, i + offs - 1);
ret.indices.emplace_back(i, i + offs, i + offs - 1);
}
ret.indices.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1);
ret.indices.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1);
return ret;
}
// Same as walls() but with identical higher and lower polygons.
indexed_triangle_set inline straight_walls(const Polygon &plate,
double lo_z,

View file

@ -36,26 +36,13 @@
#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS 1
//====================
// 2.4.0.beta1 techs
//====================
#define ENABLE_2_4_0_BETA1 1
//================
// 2.4.1.rc techs
//================
#define ENABLE_2_4_1_RC 1
// Enable rendering modifiers and similar objects always as transparent
#define ENABLE_MODIFIERS_ALWAYS_TRANSPARENT (1 && ENABLE_2_4_0_BETA1)
//====================
// 2.4.0.beta2 techs
//====================
#define ENABLE_2_4_0_BETA2 1
// Enable modified ImGuiWrapper::slider_float() to create a compound widget where
// an additional button can be used to set the keyboard focus into the slider
// to allow the user to type in the desired value
#define ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT (1 && ENABLE_2_4_0_BETA2)
// Enable fit print volume command for circular printbeds
#define ENABLE_ENHANCED_PRINT_VOLUME_FIT (1 && ENABLE_2_4_0_BETA2)
// Enable detection of layers for spiral vase prints
#define ENABLE_SPIRAL_VASE_LAYERS (1 && ENABLE_2_4_1_RC)
//====================

View file

@ -245,4 +245,35 @@ std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip)
return out;
}
indexed_triangle_set wall_strip(const Polygon &poly, double lower_z_mm, double upper_z_mm)
{
indexed_triangle_set ret;
size_t startidx = ret.vertices.size();
size_t offs = poly.points.size();
ret.vertices.reserve(ret.vertices.size() + 2 *offs);
// The expression unscaled(p).cast<float>().eval() is important here
// as it ensures identical conversion of 2D scaled coordinates to float 3D
// to that used by the tesselation. This way, the duplicated vertices in the
// output mesh can be found with the == operator of the points.
// its_merge_vertices will then reliably remove the duplicates.
for (const Point &p : poly.points)
ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(lower_z_mm)));
for (const Point &p : poly.points)
ret.vertices.emplace_back(to_3d(unscaled(p).cast<float>().eval(), float(upper_z_mm)));
for (size_t i = startidx + 1; i < startidx + offs; ++i) {
ret.indices.emplace_back(i - 1, i, i + offs - 1);
ret.indices.emplace_back(i, i + offs, i + offs - 1);
}
ret.indices.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1);
ret.indices.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1);
return ret;
}
} // namespace Slic3r

View file

@ -2,14 +2,12 @@
#define slic3r_Tesselate_hpp_
#include <vector>
#include <admesh/stl.h>
#include "Point.hpp"
#include "ExPolygon.hpp"
namespace Slic3r {
class ExPolygon;
typedef std::vector<ExPolygon> ExPolygons;
const bool constexpr NORMALS_UP = false;
const bool constexpr NORMALS_DOWN = true;
@ -20,6 +18,10 @@ extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, boo
extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = NORMALS_UP);
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = NORMALS_UP);
indexed_triangle_set wall_strip(const Polygon &poly,
double lower_z_mm,
double upper_z_mm);
} // namespace Slic3r
#endif /* slic3r_Tesselate_hpp_ */

View file

@ -74,6 +74,10 @@ extern local_encoded_string encode_path(const char *src);
extern std::string decode_path(const char *src);
extern std::string normalize_utf8_nfc(const char *src);
// Returns next utf8 sequence length. =number of bytes in string, that creates together one utf-8 character.
// Starting at pos. ASCII characters returns 1. Works also if pos is in the middle of the sequence.
extern size_t get_utf8_sequence_length(const std::string& text, size_t pos = 0);
// Safely rename a file even if the target exists.
// On Windows, the file explorer (or anti-virus or whatever else) often locks the file
// for a short while, so the file may not be movable. Retry while we see recoverable errors.

View file

@ -863,6 +863,71 @@ std::string normalize_utf8_nfc(const char *src)
return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8);
}
size_t get_utf8_sequence_length(const std::string& text, size_t pos)
{
assert(pos < text.size());
size_t length = 0;
unsigned char c = text[pos];
if (c < 0x80) { // 0x00-0x7F
// is ASCII letter
length++;
}
// Bytes 0x80 to 0xBD are trailer bytes in a multibyte sequence.
// pos is in the middle of a utf-8 sequence. Add the utf-8 trailer bytes.
else if (c < 0xC0) { // 0x80-0xBF
length++;
while (pos + length < text.size()) {
c = text[pos + length];
if (c < 0x80 || c >= 0xC0) {
break; // prevent overrun
}
length++; // add a utf-8 trailer byte
}
}
// Bytes 0xC0 to 0xFD are header bytes in a multibyte sequence.
// The number of one bits above the topmost zero bit indicates the number of bytes (including this one) in the whole sequence.
else if (c < 0xE0) { // 0xC0-0xDF
// add a utf-8 sequence (2 bytes)
if (pos + 2 > text.size()) {
return text.size() - pos; // prevent overrun
}
length += 2;
}
else if (c < 0xF0) { // 0xE0-0xEF
// add a utf-8 sequence (3 bytes)
if (pos + 3 > text.size()) {
return text.size() - pos; // prevent overrun
}
length += 3;
}
else if (c < 0xF8) { // 0xF0-0xF7
// add a utf-8 sequence (4 bytes)
if (pos + 4 > text.size()) {
return text.size() - pos; // prevent overrun
}
length += 4;
}
else if (c < 0xFC) { // 0xF8-0xFB
// add a utf-8 sequence (5 bytes)
if (pos + 5 > text.size()) {
return text.size() - pos; // prevent overrun
}
length += 5;
}
else if (c < 0xFE) { // 0xFC-0xFD
// add a utf-8 sequence (6 bytes)
if (pos + 6 > text.size()) {
return text.size() - pos; // prevent overrun
}
length += 6;
}
else { // 0xFE-0xFF
// not a utf-8 sequence
length++;
}
return length;
}
namespace PerlUtils {
// Get a file name including the extension.
std::string path_to_filename(const char *src) { return boost::filesystem::path(src).filename().string(); }

View file

@ -169,9 +169,11 @@ set(SLIC3R_GUI_SOURCES
GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp
GUI/Jobs/Job.hpp
GUI/Jobs/Job.cpp
GUI/Jobs/PlaterJob.hpp
GUI/Jobs/PlaterJob.cpp
GUI/Jobs/Worker.hpp
GUI/Jobs/BoostThreadWorker.hpp
GUI/Jobs/BoostThreadWorker.cpp
GUI/Jobs/BusyCursorJob.hpp
GUI/Jobs/PlaterWorker.hpp
GUI/Jobs/ArrangeJob.hpp
GUI/Jobs/ArrangeJob.cpp
GUI/Jobs/RotoptimizeJob.hpp
@ -183,6 +185,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/ProgressIndicator.hpp
GUI/Jobs/NotificationProgressIndicator.hpp
GUI/Jobs/NotificationProgressIndicator.cpp
GUI/Jobs/ThreadSafeQueue.hpp
GUI/Jobs/SLAImportDialog.hpp
GUI/ProgressStatusBar.hpp
GUI/ProgressStatusBar.cpp
GUI/Mouse3DController.cpp

View file

@ -477,11 +477,7 @@ ColorRGBA color_from_model_volume(const ModelVolume& model_volume)
if (model_volume.is_negative_volume())
color = { 0.2f, 0.2f, 0.2f, 1.0f };
else if (model_volume.is_modifier())
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
color = { 1.0, 1.0f, 0.2f, 1.0f };
#else
color[0] = { 0.2f, 1.0f, 0.2f, 1.0f };
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
else if (model_volume.is_support_blocker())
color = { 1.0f, 0.2f, 0.2f, 1.0f };
else if (model_volume.is_support_enforcer())
@ -502,15 +498,9 @@ std::array<float, 4> color_from_model_volume(const ModelVolume& model_volume)
color[2] = 0.2f;
}
else if (model_volume.is_modifier()) {
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
color[0] = 1.0f;
color[1] = 1.0f;
color[2] = 0.2f;
#else
color[0] = 0.2f;
color[1] = 1.0f;
color[2] = 0.2f;
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
}
else if (model_volume.is_support_blocker()) {
color[0] = 1.0f;
@ -923,15 +913,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
glsafe(::glDisable(GL_CULL_FACE));
for (GLVolumeWithIdAndZ& volume : to_render) {
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
if (type == ERenderType::Transparent)
volume.first->force_transparent = true;
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
volume.first->set_render_color();
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
if (type == ERenderType::Transparent)
volume.first->force_transparent = false;
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
// render sinking contours of non-hovered volumes
if (m_show_sinking_contours)

View file

@ -135,7 +135,7 @@ wxString CopyrightsDialog::get_html_text()
{
wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
const auto text_clr = wxGetApp().get_label_clr_default();// wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
const auto text_clr = wxGetApp().get_label_clr_default();
#if ENABLE_COLOR_CLASSES
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
@ -264,13 +264,13 @@ AboutDialog::AboutDialog()
{
m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit()));
wxFont font = get_default_font(this);
const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
const auto text_clr = wxGetApp().get_label_clr_default();
#if ENABLE_COLOR_CLASSES
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
#else
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
#endif // ENABLE_COLOR_CLASSES
const int fs = font.GetPointSize()-1;

View file

@ -81,7 +81,7 @@ std::pair<std::string, bool> SlicingProcessCompletedEvent::format_error_message(
"be glad if you reported it."))) % SLIC3R_APP_NAME).str());
error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what());
} catch (const HardCrash &ex) {
error = GUI::format("PrusaSlicer has encountered a fatal error: \"%1%\"", ex.what()) + "\n\n" +
error = GUI::format(_L("PrusaSlicer has encountered a fatal error: \"%1%\""), ex.what()) + "\n\n" +
_u8L("Please save your project and restart PrusaSlicer. "
"We would be glad if you reported the issue.");
} catch (PlaceholderParserError &ex) {

View file

@ -309,7 +309,7 @@ wxPanel* BedShapePanel::init_texture_panel()
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
if (lbl != nullptr) {
bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture);
lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED));
lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
wxString tooltip_text = "";
if (m_custom_texture != NONE) {
@ -380,7 +380,7 @@ wxPanel* BedShapePanel::init_model_panel()
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
if (lbl != nullptr) {
bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model);
lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED));
lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
wxString tooltip_text = "";
if (m_custom_model != NONE) {

View file

@ -112,7 +112,7 @@ static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const
wxString text =
"<html>"
"<body bgcolor=\"" + get_color(wxGetApp().get_window_default_clr()/*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)*/) + "\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">"
"<font color=\"" + get_color(wxGetApp().get_label_clr_default()/*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/) + "\">";
"<font color=\"" + get_color(wxGetApp().get_label_clr_default()) + "\">";
text += "<table style=\"width:100%\">";
for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) {
const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1];

View file

@ -755,7 +755,7 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
#else
const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
const auto text_clr = wxGetApp().get_label_clr_default();
const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
#endif // ENABLE_COLOR_CLASSES
wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials"));
@ -2738,8 +2738,11 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
return false;
}
else {
bool is_filaments_changed = app_config->get_section(AppConfig::SECTION_FILAMENTS) != appconfig_new.get_section(AppConfig::SECTION_FILAMENTS);
bool is_sla_materials_changed = app_config->get_section(AppConfig::SECTION_MATERIALS) != appconfig_new.get_section(AppConfig::SECTION_MATERIALS);
auto changed = [app_config, &appconfig_new = std::as_const(this->appconfig_new)](const std::string& section_name) {
return (app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>()) != appconfig_new.get_section(section_name);
};
bool is_filaments_changed = changed(AppConfig::SECTION_FILAMENTS);
bool is_sla_materials_changed = changed(AppConfig::SECTION_MATERIALS);
if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) {
header = is_filaments_changed ? _L("Some filaments were uninstalled.") : _L("Some SLA materials were uninstalled.");
if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes))

View file

@ -270,13 +270,12 @@ void FirmwareDialog::priv::flashing_start(unsigned tasks)
void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
{
auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
port_picker->Enable();
btn_rescan->Enable();
hex_picker->Enable();
btn_close->Enable();
btn_flash->SetLabel(btn_flash_label_ready);
txt_status->SetForegroundColour(text_color);
txt_status->SetForegroundColour(GUI::wxGetApp().get_label_clr_default());
timer_pulse.Stop();
progressbar->SetValue(progressbar->GetRange());
@ -811,7 +810,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) :
panel->SetSizer(vsizer);
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, wxFileSelectorPromptStr,
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, /*wxFileSelectorPromptStr*/_L("Select a file"),
"Hex files (*.hex)|*.hex|All files|*.*");
p->hex_picker->GetPickerCtrl()->SetLabelText(_(L("Browse")));

View file

@ -295,12 +295,7 @@ void GCodeViewer::SequentialView::Marker::render() const
if (width != last_window_width || length != last_text_length) {
last_window_width = width;
last_text_length = length;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.set_requires_extra_frame();
#else
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
imgui.end();
@ -2114,6 +2109,16 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
sort_remove_duplicates(m_extruder_ids);
m_extruder_ids.shrink_to_fit();
#if ENABLE_SPIRAL_VASE_LAYERS
// replace layers for spiral vase mode
if (!gcode_result.spiral_vase_layers.empty()) {
m_layers.reset();
for (const auto& layer : gcode_result.spiral_vase_layers) {
m_layers.append(layer.first, { layer.second.first, layer.second.second });
}
}
#endif // ENABLE_SPIRAL_VASE_LAYERS
// set layers z range
if (!m_layers.empty())
m_layers_z_range = { 0, static_cast<unsigned int>(m_layers.size() - 1) };
@ -3160,12 +3165,7 @@ void GCodeViewer::render_legend(float& legend_height)
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f);
// to avoid the tooltip to change size when moving the mouse
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.set_requires_extra_frame();
#else
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
}
@ -3962,12 +3962,7 @@ void GCodeViewer::render_legend(float& legend_height)
if (can_show_mode_button(mode)) {
if (imgui.button(label)) {
m_time_estimate_mode = mode;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.set_requires_extra_frame();
#else
wxGetApp().plater()->get_current_canvas3D()->set_as_dirty();
wxGetApp().plater()->get_current_canvas3D()->request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
}
};

View file

@ -482,9 +482,8 @@ class GCodeViewer
size_t first{ 0 };
size_t last{ 0 };
bool operator == (const Endpoints& other) const {
return first == other.first && last == other.last;
}
bool operator == (const Endpoints& other) const { return first == other.first && last == other.last; }
bool operator != (const Endpoints& other) const { return !operator==(other); }
};
private:
@ -513,9 +512,8 @@ class GCodeViewer
bool operator != (const Layers& other) const {
if (m_zs != other.m_zs)
return true;
if (!(m_endpoints == other.m_endpoints))
if (m_endpoints != other.m_endpoints)
return true;
return false;
}
};

View file

@ -738,13 +738,8 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
}
// force re-render while the windows gets to its final size (it takes several frames)
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
imgui.set_requires_extra_frame();
#else
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
m_canvas.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
imgui.end();
ImGui::PopStyleColor();
@ -790,13 +785,8 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
ImGui::TextUnformatted(m_text.c_str());
// force re-render while the windows gets to its final size (it may take several frames) or while hidden
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
imgui.set_requires_extra_frame();
#else
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
canvas.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
size = ImGui::GetWindowSize();
@ -1138,10 +1128,9 @@ void GLCanvas3D::reset_volumes()
ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const
{
assert(m_initialized);
ModelInstanceEPrintVolumeState state;
m_volumes.check_outside_state(m_bed.build_volume(), &state);
ModelInstanceEPrintVolumeState state = ModelInstanceEPrintVolumeState::ModelInstancePVS_Inside;
if (m_initialized)
m_volumes.check_outside_state(m_bed.build_volume(), &state);
return state;
}
@ -1315,15 +1304,6 @@ bool GLCanvas3D::is_reload_delayed() const
void GLCanvas3D::enable_layers_editing(bool enable)
{
m_layers_editing.set_enabled(enable);
#if !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
const Selection::IndicesList& idxs = m_selection.get_volume_idxs();
for (unsigned int idx : idxs) {
GLVolume* v = m_volumes.volumes[idx];
if (v->is_modifier)
v->force_transparent = enable;
}
#endif // !ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
set_as_dirty();
}
@ -2266,29 +2246,20 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
auto gizmo = wxGetApp().plater()->canvas3D()->get_gizmos_manager().get_current();
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
// ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism
bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame();
m_dirty |= imgui_requires_extra_frame;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (!m_dirty)
return;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
// this needs to be done here.
// during the render launched by the refresh the value may be set again
wxGetApp().imgui()->reset_requires_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
_refresh_if_shown_on_screen();
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
#else
if (m_extra_frame_requested || mouse3d_controller_applied) {
m_dirty = true;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_extra_frame_requested = false;
evt.RequestMore();
}

View file

@ -1146,15 +1146,27 @@ bool GUI_App::on_init_inner()
// Detect position (display) to show the splash screen
// Now this position is equal to the mainframe position
wxPoint splashscreen_pos = wxDefaultPosition;
if (app_config->has("window_mainframe")) {
bool default_splashscreen_pos = true;
if (app_config->has("window_mainframe") && app_config->get("restore_win_position") == "1") {
auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe"));
if (metrics)
default_splashscreen_pos = metrics == boost::none;
if (!default_splashscreen_pos)
splashscreen_pos = metrics->get_rect().GetPosition();
}
if (!default_splashscreen_pos) {
// workaround for crash related to the positioning of the window on secondary monitor
get_app_config()->set("restore_win_position", "crashed_at_splashscreen_pos");
get_app_config()->save();
}
// create splash screen with updated bmp
scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("PrusaSlicer", nullptr, 400),
wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos);
if (!default_splashscreen_pos)
// revert "restore_win_position" value if application wasn't crashed
get_app_config()->set("restore_win_position", "1");
#ifndef __linux__
wxYield();
#endif
@ -1311,6 +1323,35 @@ bool GUI_App::on_init_inner()
});
m_initialized = true;
if (const std::string& crash_reason = app_config->get("restore_win_position");
boost::starts_with(crash_reason,"crashed"))
{
wxString preferences_item = _L("Restore window position on start");
InfoDialog dialog(nullptr,
_L("PrusaSlicer is started in save mode"),
format_wxstr(_L("PrusaSlicer was crashed last time due to \"%1%\".\n"
"For more information see issues \"%2%\" and \"%3%\"\n\n"
"To avoid an application crash next time you have to disable\n"
"\"%4%\" in \"Preferences\""),
"<b>" + from_u8(crash_reason) + "</b>",
"<a href=http://github.com/prusa3d/PrusaSlicer/issues/2939>#2939</a>",
"<a href=http://github.com/prusa3d/PrusaSlicer/issues/5573>#5573</a>",
"<b>" + preferences_item + "</b>")
+ "\n\n" +
format_wxstr(_L("Note: Enabling of the \"%1%\" will caused an application crash on next start."), preferences_item),
true, wxYES_NO);
dialog.SetButtonLabel(wxID_YES, format_wxstr(_L("Disable \"%1%\""), preferences_item));
dialog.SetButtonLabel(wxID_NO, format_wxstr(_L("Enable \"%1%\"") , preferences_item));
auto answer = dialog.ShowModal();
if (answer == wxID_YES)
app_config->set("restore_win_position", "0");
else if (answer == wxID_NO)
app_config->set("restore_win_position", "1");
}
return true;
}
@ -1391,11 +1432,13 @@ void GUI_App::update_label_colours()
tab->update_label_colours();
}
#ifdef _WIN32
static bool is_focused(HWND hWnd)
{
HWND hFocusedWnd = ::GetFocus();
return hFocusedWnd && hWnd == hFocusedWnd;
}
#endif
void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/)
{
@ -2770,7 +2813,7 @@ wxString GUI_App::current_language_code_safe() const
void GUI_App::open_web_page_localized(const std::string &http_address)
{
open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe());
open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe(), nullptr, false);
}
// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s).
@ -2912,8 +2955,24 @@ void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &na
}
const wxRect& rect = metrics->get_rect();
window->SetPosition(rect.GetPosition());
window->SetSize(rect.GetSize());
if (app_config->get("restore_win_position") == "1") {
// workaround for crash related to the positioning of the window on secondary monitor
app_config->set("restore_win_position", (boost::format("crashed_at_%1%_pos") % name).str());
app_config->save();
window->SetPosition(rect.GetPosition());
// workaround for crash related to the positioning of the window on secondary monitor
app_config->set("restore_win_position", (boost::format("crashed_at_%1%_size") % name).str());
app_config->save();
window->SetSize(rect.GetSize());
// revert "restore_win_position" value if application wasn't crashed
app_config->set("restore_win_position", "1");
}
else
window->CenterOnScreen();
window->Maximize(metrics->get_maximized());
}
@ -2974,19 +3033,40 @@ void GUI_App::check_updates(const bool verbose)
}
}
bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* = 0*/)
bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/)
{
bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) {
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal();
launch = answer == wxID_YES;
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
// warning dialog containes a "Remember my choice" checkbox
std::string option_key = "suppress_hyperlinks";
if (force_remember_choice || app_config->get(option_key).empty()) {
if (app_config->get(option_key).empty()) {
RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
auto answer = dialog.ShowModal();
launch = answer == wxID_YES;
if (dialog.IsCheckBoxChecked()) {
wxString preferences_item = _L("Suppress to open hyperlink in browser");
wxString msg =
_L("PrusaSlicer will remember your choice.") + "\n\n" +
_L("You will not be asked about it again on hyperlinks hovering.") + "\n\n" +
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
if (msg_dlg.ShowModal() == wxID_CANCEL)
return false;
app_config->set(option_key, answer == wxID_NO ? "1" : "0");
}
}
if (launch)
launch = app_config->get(option_key) != "1";
}
// warning dialog doesn't containe a "Remember my choice" checkbox
// and will be shown only when "Suppress to open hyperlink in browser" is ON.
else if (app_config->get(option_key) == "1") {
MessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO);
launch = dialog.ShowModal() == wxID_YES;
}
if (launch)
launch = get_app_config()->get("suppress_hyperlinks") != "1";
return launch && wxLaunchDefaultBrowser(url, flags);
}

View file

@ -268,7 +268,8 @@ public:
virtual bool OnExceptionInMainLoop() override;
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
bool open_browser_with_warning_dialog(const wxString& url, int flags = 0);
// Add "Rememeber my choice" checkbox to question dialog, when it is forced or a "suppress_hyperlinks" option has empty value
bool open_browser_with_warning_dialog(const wxString& url, wxWindow* parent = nullptr, bool force_remember_choice = true, int flags = 0);
#ifdef __APPLE__
void OSXStoreOpenFiles(const wxArrayString &files) override;
// wxWidgets override to get an event on open files.

View file

@ -716,7 +716,7 @@ void MenuFactory::append_menu_item_export_stl(wxMenu* menu)
[](wxCommandEvent&) { plater()->export_stl(false, true); }, "", nullptr,
[]() {
const Selection& selection = plater()->canvas3D()->get_selection();
return selection.is_single_full_instance() || selection.is_single_full_object();
return selection.is_single_full_instance() || selection.is_single_full_object() || selection.is_single_volume() || selection.is_single_modifier();
}, m_parent);
menu->AppendSeparator();
}
@ -792,14 +792,9 @@ void MenuFactory::append_menu_item_change_extruder(wxMenu* menu)
void MenuFactory::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu)
{
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"),
[](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu,
[]() { return plater()->can_scale_to_print_volume(); }, m_parent);
#else
append_menu_item(menu, wxID_ANY, _L("Scale to print volume"), _L("Scale the selected object to fit the print volume"),
[](wxCommandEvent&) { plater()->scale_selection_to_fit_print_volume(); }, "", menu);
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
}
void MenuFactory::append_menu_items_convert_unit(wxMenu* menu, int insert_pos/* = 1*/)

View file

@ -9,6 +9,8 @@
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/I18N.hpp"
// To show a message box if GUI initialization ends up with an exception thrown.
#include <wx/msgdlg.h>

View file

@ -495,7 +495,7 @@ void ObjectManipulation::update_ui_from_settings()
// update colors for edit-boxes
int axis_id = 0;
for (ManipulationEditor* editor : m_editors) {
// editor->SetForegroundColour(m_use_colors ? wxColour(axes_color_text[axis_id]) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
// editor->SetForegroundColour(m_use_colors ? wxColour(axes_color_text[axis_id]) : wxGetApp().get_label_clr_default());
if (m_use_colors) {
editor->SetBackgroundColour(wxColour(axes_color_back[axis_id]));
if (wxGetApp().dark_mode())

View file

@ -774,7 +774,8 @@ void Preview::update_layers_slider_mode()
return false;
for (ModelVolume* volume : object->volumes)
if ((volume->config.has("extruder") &&
if ((volume->config.has("extruder") &&
volume->config.option("extruder")->getInt() != 0 && // extruder isn't default
volume->config.option("extruder")->getInt() != extruder) ||
!volume->mmu_segmentation_facets.empty())
return false;

View file

@ -180,11 +180,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
if (last_h != win_h || last_y != y) {
// ask canvas for another frame to render the window in the correct position
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->set_requires_extra_frame();
#else
m_parent.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (last_h != win_h)
last_h = win_h;
if (last_y != y)

View file

@ -134,12 +134,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(std::max(autoset_slider_left, smart_fill_slider_left), std::max(cursor_slider_left, clipping_slider_left));
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
#else
float window_width = minimal_slider_width + sliders_left_width;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, split_triangles_checkbox_width);
@ -174,15 +170,10 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
float slider_start_position_y = std::max(position_before_text_y, position_after_text_y - slider_height);
ImGui::SetCursorPosY(slider_start_position_y);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
wxString tooltip = format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
"the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]);
if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data(), 1.0f, true, tooltip)) {
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
if (m_imgui->slider_float("##angle_threshold_deg", &m_highlight_by_angle_threshold_deg, 0.f, 90.f, format_str.data())) {
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_parent.set_slope_normal_angle(90.f - m_highlight_by_angle_threshold_deg);
if (! m_parent.is_using_slope()) {
m_parent.use_slope(true);
@ -194,11 +185,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::SetCursorPosY(std::max(position_before_text_y + slider_height, position_after_text_y));
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::IsItemHovered())
m_imgui->tooltip(format_wxstr(_L("Preselects faces by overhang angle. It is possible to restrict paintable facets to only preselected faces when "
"the option \"%1%\" is enabled."), m_desc["on_overhangs_only"]), max_tooltip_width);
#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->disabled_begin(m_highlight_by_angle_threshold_deg == 0.f);
ImGui::NewLine();
@ -280,15 +266,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
@ -302,22 +281,12 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_imgui->text(m_desc["smart_fill_angle"] + ":");
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data()))
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
}
ImGui::Separator();
@ -335,18 +304,9 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true);
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {

View file

@ -162,9 +162,9 @@ void GLGizmoFlatten::update_planes()
std::vector<bool> facet_visited(num_of_facets, false);
int facet_queue_cnt = 0;
const stl_normal* normal_ptr = nullptr;
int facet_idx = 0;
while (1) {
// Find next unvisited triangle:
int facet_idx = 0;
for (; facet_idx < num_of_facets; ++ facet_idx)
if (!facet_visited[facet_idx]) {
facet_queue[facet_queue_cnt ++] = facet_idx;
@ -261,7 +261,8 @@ void GLGizmoFlatten::update_planes()
}
if (discard) {
m_planes.erase(m_planes.begin() + (polygon_id--));
m_planes[polygon_id--] = std::move(m_planes.back());
m_planes.pop_back();
continue;
}

View file

@ -340,12 +340,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(smart_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
#else
float window_width = minimal_slider_width + sliders_left_width;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, split_triangles_checkbox_width);
@ -487,15 +483,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->checkbox(m_desc["split_triangles"], m_triangle_splitting_enabled);
@ -511,23 +500,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data(), 1.0f, true, _L("Alt + Mouse wheel")))
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
if(m_imgui->slider_float("##smart_fill_angle", &m_smart_fill_angle, SmartFillAngleMin, SmartFillAngleMax, format_str.data()))
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
#if !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // !ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
}
@ -542,18 +521,9 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true);
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {

View file

@ -558,11 +558,12 @@ GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui,
ImVec2 button_sz = {btn_txt_sz.x + padding.x, btn_txt_sz.y + padding.y};
ImGui::SetCursorPosX(padding.x + sz.x - button_sz.x);
if (wxGetApp().plater()->is_any_job_running())
if (!wxGetApp().plater()->get_ui_job_worker().is_idle())
imgui->disabled_begin(true);
if ( imgui->button(btn_txt) ) {
wxGetApp().plater()->optimize_rotation();
replace_job(wxGetApp().plater()->get_ui_job_worker(),
std::make_unique<RotoptimizeJob>());
}
imgui->disabled_end();

View file

@ -2,7 +2,6 @@
#define slic3r_GLGizmoRotate_hpp_
#include "GLGizmoBase.hpp"
#include "../Jobs/RotoptimizeJob.hpp"
namespace Slic3r {
namespace GUI {

View file

@ -105,12 +105,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
caption_max += m_imgui->scaled(1.f);
const float sliders_left_width = std::max(cursor_size_slider_left, clipping_slider_left);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
const float slider_icon_width = m_imgui->get_slider_icon_size().x;
float window_width = minimal_slider_width + sliders_left_width + slider_icon_width;
#else
float window_width = minimal_slider_width + sliders_left_width;
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_sphere + cursor_type_radio_circle);
@ -132,15 +128,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f", 1.0f, true, _L("Alt + Mouse wheel"));
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
m_imgui->slider_float("##cursor_radius", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Alt + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
@ -177,18 +166,9 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
auto clp_dist = float(m_c->object_clipper()->get_position());
ImGui::SameLine(sliders_left_width);
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::PushItemWidth(window_width - sliders_left_width - slider_icon_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true, _L("Ctrl + Mouse wheel")))
m_c->object_clipper()->set_position(clp_dist, true);
#else
ImGui::PushItemWidth(window_width - sliders_left_width);
if (m_imgui->slider_float("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered())
m_imgui->tooltip(_L("Ctrl + Mouse wheel"), max_tooltip_width);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {

View file

@ -659,11 +659,7 @@ RENDER_AGAIN:
if ((last_h != win_h) || (last_y != y))
{
// ask canvas for another frame to render the window in the correct position
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
m_imgui->set_requires_extra_frame();
#else
m_parent.request_extra_frame();
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
if (last_h != win_h)
last_h = win_h;
if (last_y != y)

View file

@ -615,7 +615,7 @@ void NotificationManager::HintNotification::count_lines()
float width_of_a = ImGui::CalcTextSize("a").x;
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
letter_count++;
letter_count += get_utf8_sequence_length(text, last_end + letter_count);
}
m_endlines.push_back(last_end + letter_count);
last_end += letter_count;
@ -685,7 +685,7 @@ void NotificationManager::HintNotification::count_lines()
float width_of_a = ImGui::CalcTextSize("a").x;
int letter_count = (int)((m_window_width - m_window_width_offset - size_of_last_line) / width_of_a);
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset - size_of_last_line) {
letter_count++;
letter_count += get_utf8_sequence_length(text, last_end + letter_count);
}
m_endlines2.push_back(last_end + letter_count);
last_end += letter_count;
@ -1058,4 +1058,4 @@ void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true
}
}
} //namespace Slic3r
} //namespace GUI
} //namespace GUI

View file

@ -8,10 +8,8 @@
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/convert.hpp>
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
#include <wx/string.h>
#include <wx/event.h>
@ -54,11 +52,9 @@ static const std::map<const wchar_t, std::string> font_icons = {
{ImGui::MinimalizeHoverButton , "notification_minimalize_hover" },
{ImGui::RightArrowButton , "notification_right" },
{ImGui::RightArrowHoverButton , "notification_right_hover" },
{ImGui::PreferencesButton , "notification_preferences" },
{ImGui::PreferencesHoverButton , "notification_preferences_hover"},
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
{ImGui::SliderFloatEditBtnIcon, "edit_button" },
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
{ImGui::PreferencesButton , "notification_preferences" },
{ImGui::PreferencesHoverButton, "notification_preferences_hover"},
{ImGui::SliderFloatEditBtnIcon, "edit_button" },
};
static const std::map<const wchar_t, std::string> font_icons_large = {
{ImGui::CloseNotifButton , "notification_close" },
@ -497,7 +493,6 @@ void ImGuiWrapper::tooltip(const wxString &label, float wrap_width)
ImGui::EndTooltip();
}
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImVec2 ImGuiWrapper::get_slider_icon_size() const
{
return this->calc_button_size(std::wstring(&ImGui::SliderFloatEditBtnIcon, 1));
@ -574,26 +569,6 @@ bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, fl
auto label_utf8 = into_u8(label);
return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp, tooltip, show_edit_btn);
}
#else
bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/)
{
bool ret = ImGui::SliderFloat(label, v, v_min, v_max, format, power);
if (clamp)
*v = std::clamp(*v, v_min, v_max);
return ret;
}
bool ImGuiWrapper::slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/)
{
return this->slider_float(label.c_str(), v, v_min, v_max, format, power, clamp);
}
bool ImGuiWrapper::slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/, bool clamp /*= true*/)
{
auto label_utf8 = into_u8(label);
return this->slider_float(label_utf8.c_str(), v, v_min, v_max, format, power, clamp);
}
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool ImGuiWrapper::combo(const wxString& label, const std::vector<std::string>& options, int& selection)
{

View file

@ -38,9 +38,7 @@ class ImGuiWrapper
unsigned m_mouse_buttons{ 0 };
bool m_disabled{ false };
bool m_new_frame_open{ false };
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool m_requires_extra_frame{ false };
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
std::string m_clipboard_text;
public:
@ -108,16 +106,10 @@ public:
void tooltip(const wxString &label, float wrap_width);
// Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true).
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
ImVec2 get_slider_icon_size() const;
bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true);
bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true);
bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true, const wxString& tooltip = {}, bool show_edit_btn = true);
#else
bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true);
bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true);
bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f, bool clamp = true);
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool combo(const wxString& label, const std::vector<std::string>& options, int& selection); // Use -1 to not mark any option as selected
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel);
@ -133,11 +125,9 @@ public:
bool want_text_input() const;
bool want_any_input() const;
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
bool requires_extra_frame() const { return m_requires_extra_frame; }
void set_requires_extra_frame() { m_requires_extra_frame = true; }
void reset_requires_extra_frame() { m_requires_extra_frame = false; }
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
#if ENABLE_COLOR_CLASSES
static ImU32 to_ImU32(const ColorRGBA& color);

View file

@ -199,7 +199,10 @@ namespace instance_check_internal
//else
// BOOST_LOG_TRIVIAL(error) << "success delete lockfile " << path;
#ifdef __APPLE__
send_message_mac_closing(GUI::wxGetApp().get_instance_hash_string(),GUI::wxGetApp().get_instance_hash_string());
// Partial fix of #7583
// On price of incorrect working of single instances on older OSX
if (wxPlatformInfo::Get().GetOSMajorVersion() > 12)
send_message_mac_closing(GUI::wxGetApp().get_instance_hash_string(),GUI::wxGetApp().get_instance_hash_string());
#endif
}
}

View file

@ -162,49 +162,43 @@ void ArrangeJob::prepare()
wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all();
}
void ArrangeJob::on_exception(const std::exception_ptr &eptr)
void ArrangeJob::process(Ctl &ctl)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (libnest2d::GeometryException &) {
show_error(m_plater, _(L("Could not arrange model objects! "
"Some geometries may be invalid.")));
} catch (std::exception &) {
PlaterJob::on_exception(eptr);
}
}
static const auto arrangestr = _u8L("Arranging");
void ArrangeJob::process()
{
static const auto arrangestr = _(L("Arranging"));
ctl.update_status(0, arrangestr);
ctl.call_on_main_thread([this]{ prepare(); }).wait();;
arrangement::ArrangeParams params = get_arrange_params(m_plater);
auto count = unsigned(m_selected.size() + m_unprintable.size());
auto count = unsigned(m_selected.size() + m_unprintable.size());
Points bedpts = get_bed_shape(*m_plater->config());
params.stopcondition = [this]() { return was_canceled(); };
params.progressind = [this, count](unsigned st) {
params.stopcondition = [&ctl]() { return ctl.was_canceled(); };
params.progressind = [this, count, &ctl](unsigned st) {
st += m_unprintable.size();
if (st > 0) update_status(int(count - st), arrangestr);
if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr);
};
ctl.update_status(0, arrangestr);
arrangement::arrange(m_selected, m_unselected, bedpts, params);
params.progressind = [this, count](unsigned st) {
if (st > 0) update_status(int(count - st), arrangestr);
params.progressind = [this, count, &ctl](unsigned st) {
if (st > 0) ctl.update_status(int(count - st) * 100 / status_range(), arrangestr);
};
arrangement::arrange(m_unprintable, {}, bedpts, params);
// finalize just here.
update_status(int(count),
was_canceled() ? _(L("Arranging canceled."))
: _(L("Arranging done.")));
ctl.update_status(int(count) * 100 / status_range(), ctl.was_canceled() ?
_u8L("Arranging canceled.") :
_u8L("Arranging done."));
}
ArrangeJob::ArrangeJob() : m_plater{wxGetApp().plater()} {}
static std::string concat_strings(const std::set<std::string> &strings,
const std::string &delim = "\n")
{
@ -215,10 +209,21 @@ static std::string concat_strings(const std::set<std::string> &strings,
});
}
void ArrangeJob::finalize() {
// Ignore the arrange result if aborted.
if (was_canceled()) return;
void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) {
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (libnest2d::GeometryException &) {
show_error(m_plater, _(L("Could not arrange model objects! "
"Some geometries may be invalid.")));
eptr = nullptr;
} catch(...) {
eptr = std::current_exception();
}
if (canceled || eptr)
return;
// Unprintable items go to the last virtual bed
int beds = 0;
@ -250,8 +255,6 @@ void ArrangeJob::finalize() {
_L("Arrangement ignored the following objects which can't fit into a single bed:\n%s"),
concat_strings(names, "\n")));
}
Job::finalize();
}
std::optional<arrangement::ArrangePolygon>

View file

@ -1,7 +1,9 @@
#ifndef ARRANGEJOB_HPP
#define ARRANGEJOB_HPP
#include "PlaterJob.hpp"
#include <optional>
#include "Job.hpp"
#include "libslic3r/Arrange.hpp"
namespace Slic3r {
@ -10,13 +12,16 @@ class ModelInstance;
namespace GUI {
class ArrangeJob : public PlaterJob
class Plater;
class ArrangeJob : public Job
{
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
ArrangePolygons m_selected, m_unselected, m_unprintable;
std::vector<ModelInstance*> m_unarranged;
Plater *m_plater;
// clear m_selected and m_unselected, reserve space for next usage
void clear_input();
@ -30,25 +35,20 @@ class ArrangeJob : public PlaterJob
ArrangePolygon get_arrange_poly_(ModelInstance *mi);
protected:
void prepare() override;
void on_exception(const std::exception_ptr &) override;
void process() override;
public:
ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}
{}
int status_range() const override
void prepare();
void process(Ctl &ctl) override;
ArrangeJob();
int status_range() const
{
return int(m_selected.size() + m_unprintable.size());
}
void finalize() override;
void finalize(bool canceled, std::exception_ptr &e) override;
};
std::optional<arrangement::ArrangePolygon> get_wipe_tower_arrangepoly(const Plater &);

View file

@ -0,0 +1,181 @@
#include <exception>
#include "BoostThreadWorker.hpp"
namespace Slic3r { namespace GUI {
void BoostThreadWorker::WorkerMessage::deliver(BoostThreadWorker &runner)
{
switch(MsgType(get_type())) {
case Empty: break;
case Status: {
auto info = boost::get<StatusInfo>(m_data);
if (runner.get_pri()) {
runner.get_pri()->set_progress(info.status);
runner.get_pri()->set_status_text(info.msg.c_str());
}
break;
}
case Finalize: {
auto& entry = boost::get<JobEntry>(m_data);
entry.job->finalize(entry.canceled, entry.eptr);
// Unhandled exceptions are rethrown without mercy.
if (entry.eptr)
std::rethrow_exception(entry.eptr);
break;
}
case MainThreadCall: {
auto &calldata = boost::get<MainThreadCallData >(m_data);
calldata.fn();
calldata.promise.set_value();
break;
}
}
}
void BoostThreadWorker::run()
{
bool stop = false;
while (!stop) {
m_input_queue
.consume_one(BlockingWait{0, &m_running}, [this, &stop](JobEntry &e) {
if (!e.job)
stop = true;
else {
m_canceled.store(false);
try {
e.job->process(*this);
} catch (...) {
e.eptr = std::current_exception();
}
e.canceled = m_canceled.load();
m_output_queue.push(std::move(e)); // finalization message
}
m_running.store(false);
});
};
}
void BoostThreadWorker::update_status(int st, const std::string &msg)
{
m_output_queue.push(st, msg);
}
std::future<void> BoostThreadWorker::call_on_main_thread(std::function<void ()> fn)
{
MainThreadCallData cbdata{std::move(fn), {}};
std::future<void> future = cbdata.promise.get_future();
m_output_queue.push(std::move(cbdata));
return future;
}
BoostThreadWorker::BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
boost::thread::attributes &attribs,
const char * name)
: m_progress(std::move(pri)), m_name{name}
{
if (m_progress)
m_progress->set_cancel_callback([this](){ cancel(); });
m_thread = create_thread(attribs, [this] { this->run(); });
std::string nm{name};
if (!nm.empty()) set_thread_name(m_thread, name);
}
constexpr int ABORT_WAIT_MAX_MS = 10000;
BoostThreadWorker::~BoostThreadWorker()
{
bool joined = false;
try {
cancel_all();
wait_for_idle(ABORT_WAIT_MAX_MS);
m_input_queue.push(JobEntry{nullptr});
joined = join(ABORT_WAIT_MAX_MS);
} catch(...) {}
if (!joined)
BOOST_LOG_TRIVIAL(error)
<< "Could not join worker thread '" << m_name << "'";
}
bool BoostThreadWorker::join(int timeout_ms)
{
if (!m_thread.joinable())
return true;
if (timeout_ms <= 0) {
m_thread.join();
}
else if (m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) {
return true;
}
else
return false;
return true;
}
void BoostThreadWorker::process_events()
{
while (m_output_queue.consume_one([this](WorkerMessage &msg) {
msg.deliver(*this);
}));
}
bool BoostThreadWorker::wait_for_current_job(unsigned timeout_ms)
{
bool ret = true;
if (!is_idle()) {
bool was_finish = false;
bool timeout_reached = false;
while (!timeout_reached && !was_finish) {
timeout_reached =
!m_output_queue.consume_one(BlockingWait{timeout_ms},
[this, &was_finish](
WorkerMessage &msg) {
msg.deliver(*this);
if (msg.get_type() ==
WorkerMessage::Finalize)
was_finish = true;
});
}
ret = !timeout_reached;
}
return ret;
}
bool BoostThreadWorker::wait_for_idle(unsigned timeout_ms)
{
bool timeout_reached = false;
while (!timeout_reached && !is_idle()) {
timeout_reached = !m_output_queue
.consume_one(BlockingWait{timeout_ms},
[this](WorkerMessage &msg) {
msg.deliver(*this);
});
}
return !timeout_reached;
}
bool BoostThreadWorker::push(std::unique_ptr<Job> job)
{
if (job)
m_input_queue.push(JobEntry{std::move(job)});
return bool{job};
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,140 @@
#ifndef BOOSTTHREADWORKER_HPP
#define BOOSTTHREADWORKER_HPP
#include <boost/variant.hpp>
#include "Worker.hpp"
#include <libslic3r/Thread.hpp>
#include <boost/log/trivial.hpp>
#include "ThreadSafeQueue.hpp"
namespace Slic3r { namespace GUI {
// An implementation of the Worker interface which uses the boost::thread
// API and two thread safe message queues to communicate with the main thread
// back and forth. The queue from the main thread to the worker thread holds the
// job entries that will be performed on the worker. The other queue holds messages
// from the worker to the main thread. These messages include status updates,
// finishing operation and arbitrary functiors that need to be performed
// on the main thread during the jobs execution, like displaying intermediate
// results.
class BoostThreadWorker : public Worker, private Job::Ctl
{
struct JobEntry // Goes into worker and also out of worker as a finalize msg
{
std::unique_ptr<Job> job;
bool canceled = false;
std::exception_ptr eptr = nullptr;
};
// A message data for status updates. Only goes from worker to main thread.
struct StatusInfo { int status; std::string msg; };
// An arbitrary callback to be called on the main thread. Only from worker
// to main thread.
struct MainThreadCallData
{
std::function<void()> fn;
std::promise<void> promise;
};
struct EmptyMessage {};
class WorkerMessage
{
public:
enum MsgType { Empty, Status, Finalize, MainThreadCall };
private:
boost::variant<EmptyMessage, StatusInfo, JobEntry, MainThreadCallData> m_data;
public:
WorkerMessage() = default;
WorkerMessage(int s, std::string txt)
: m_data{StatusInfo{s, std::move(txt)}}
{}
WorkerMessage(JobEntry &&entry) : m_data{std::move(entry)} {}
WorkerMessage(MainThreadCallData fn) : m_data{std::move(fn)} {}
int get_type () const { return m_data.which(); }
void deliver(BoostThreadWorker &runner);
};
using JobQueue = ThreadSafeQueueSPSC<JobEntry>;
using MessageQueue = ThreadSafeQueueSPSC<WorkerMessage>;
boost::thread m_thread;
std::atomic<bool> m_running{false}, m_canceled{false};
std::shared_ptr<ProgressIndicator> m_progress;
JobQueue m_input_queue; // from main thread to worker
MessageQueue m_output_queue; // form worker to main thread
std::string m_name;
void run();
bool join(int timeout_ms = 0);
protected:
// Implement Job::Ctl interface:
void update_status(int st, const std::string &msg = "") override;
bool was_canceled() const override { return m_canceled.load(); }
std::future<void> call_on_main_thread(std::function<void()> fn) override;
public:
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
boost::thread::attributes & attr,
const char * name = "");
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
boost::thread::attributes && attr,
const char * name = "")
: BoostThreadWorker{std::move(pri), attr, name}
{}
explicit BoostThreadWorker(std::shared_ptr<ProgressIndicator> pri,
const char * name = "")
: BoostThreadWorker{std::move(pri), {}, name}
{}
~BoostThreadWorker();
BoostThreadWorker(const BoostThreadWorker &) = delete;
BoostThreadWorker(BoostThreadWorker &&) = delete;
BoostThreadWorker &operator=(const BoostThreadWorker &) = delete;
BoostThreadWorker &operator=(BoostThreadWorker &&) = delete;
bool push(std::unique_ptr<Job> job) override;
bool is_idle() const override
{
// The assumption is that jobs can only be queued from a single main
// thread from which this method is also called. And the output
// messages are also processed only in this calling thread. In that
// case, if the input queue is empty, it will remain so during this
// function call. If the worker thread is also not running and the
// output queue is already processed, we can safely say that the
// worker is dormant.
return m_input_queue.empty() && !m_running.load() && m_output_queue.empty();
}
void cancel() override { m_canceled.store(true); }
void cancel_all() override { m_input_queue.clear(); cancel(); }
ProgressIndicator * get_pri() { return m_progress.get(); }
const ProgressIndicator * get_pri() const { return m_progress.get(); }
void process_events() override;
bool wait_for_current_job(unsigned timeout_ms = 0) override;
bool wait_for_idle(unsigned timeout_ms = 0) override;
};
}} // namespace Slic3r::GUI
#endif // BOOSTTHREADWORKER_HPP

View file

@ -0,0 +1,48 @@
#ifndef BUSYCURSORJOB_HPP
#define BUSYCURSORJOB_HPP
#include "Job.hpp"
#include <wx/utils.h>
namespace Slic3r { namespace GUI {
struct CursorSetterRAII
{
Job::Ctl &ctl;
CursorSetterRAII(Job::Ctl &c) : ctl{c}
{
ctl.call_on_main_thread([] { wxBeginBusyCursor(); });
}
~CursorSetterRAII()
{
ctl.call_on_main_thread([] { wxEndBusyCursor(); });
}
};
template<class JobSubclass>
class BusyCursored: public Job {
JobSubclass m_job;
public:
template<class... Args>
BusyCursored(Args &&...args) : m_job{std::forward<Args>(args)...}
{}
void process(Ctl &ctl) override
{
CursorSetterRAII cursor_setter{ctl};
m_job.process(ctl);
}
void finalize(bool canceled, std::exception_ptr &eptr) override
{
m_job.finalize(canceled, eptr);
}
};
}
}
#endif // BUSYCURSORJOB_HPP

View file

@ -3,6 +3,7 @@
#include "libslic3r/Model.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
@ -102,8 +103,12 @@ void FillBedJob::prepare()
p.translation(X) -= p.bed_idx * stride;
}
void FillBedJob::process()
void FillBedJob::process(Ctl &ctl)
{
auto statustxt = _u8L("Filling bed");
ctl.call_on_main_thread([this] { prepare(); }).wait();
ctl.update_status(0, statustxt);
if (m_object_idx == -1 || m_selected.empty()) return;
const GLCanvas3D::ArrangeSettings &settings =
@ -114,13 +119,13 @@ void FillBedJob::process()
params.min_obj_distance = scaled(settings.distance);
bool do_stop = false;
params.stopcondition = [this, &do_stop]() {
return was_canceled() || do_stop;
params.stopcondition = [&ctl, &do_stop]() {
return ctl.was_canceled() || do_stop;
};
params.progressind = [this](unsigned st) {
params.progressind = [this, &ctl, &statustxt](unsigned st) {
if (st > 0)
update_status(int(m_status_range - st), _(L("Filling bed")));
ctl.update_status(int(m_status_range - st) * 100 / status_range(), statustxt);
};
params.on_packed = [&do_stop] (const ArrangePolygon &ap) {
@ -130,15 +135,18 @@ void FillBedJob::process()
arrangement::arrange(m_selected, m_unselected, m_bedpts, params);
// finalize just here.
update_status(m_status_range, was_canceled() ?
_(L("Bed filling canceled.")) :
_(L("Bed filling done.")));
ctl.update_status(100, ctl.was_canceled() ?
_u8L("Bed filling canceled.") :
_u8L("Bed filling done."));
}
void FillBedJob::finalize()
FillBedJob::FillBedJob() : m_plater{wxGetApp().plater()} {}
void FillBedJob::finalize(bool canceled, std::exception_ptr &eptr)
{
// Ignore the arrange result if aborted.
if (was_canceled()) return;
if (canceled || eptr)
return;
if (m_object_idx == -1) return;
@ -167,8 +175,6 @@ void FillBedJob::finalize()
m_plater->sidebar()
.obj_list()->increase_object_instances(m_object_idx, size_t(added_cnt));
}
Job::finalize();
}
}} // namespace Slic3r::GUI

View file

@ -7,9 +7,9 @@ namespace Slic3r { namespace GUI {
class Plater;
class FillBedJob : public PlaterJob
class FillBedJob : public Job
{
int m_object_idx = -1;
int m_object_idx = -1;
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
@ -20,23 +20,20 @@ class FillBedJob : public PlaterJob
Points m_bedpts;
int m_status_range = 0;
protected:
void prepare() override;
void process() override;
Plater *m_plater;
public:
FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}
{}
void prepare();
void process(Ctl &ctl) override;
int status_range() const override
FillBedJob();
int status_range() const /*override*/
{
return m_status_range;
}
void finalize() override;
void finalize(bool canceled, std::exception_ptr &e) override;
};
}} // namespace Slic3r::GUI

View file

@ -1,158 +0,0 @@
#include <algorithm>
#include <exception>
#include "Job.hpp"
#include <libslic3r/Thread.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r {
void GUI::Job::run(std::exception_ptr &eptr)
{
m_running.store(true);
try {
process();
} catch (...) {
eptr = std::current_exception();
}
m_running.store(false);
// ensure to call the last status to finalize the job
update_status(status_range(), "");
}
void GUI::Job::update_status(int st, const wxString &msg)
{
auto evt = new wxThreadEvent(wxEVT_THREAD, m_thread_evt_id);
evt->SetInt(st);
evt->SetString(msg);
wxQueueEvent(this, evt);
}
GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri)
: m_progress(std::move(pri))
{
m_thread_evt_id = wxNewId();
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
if (m_finalizing) return;
auto msg = evt.GetString();
if (!msg.empty() && !m_worker_error)
m_progress->set_status_text(msg.ToUTF8().data());
if (m_finalized) return;
m_progress->set_progress(evt.GetInt());
if (evt.GetInt() == status_range() || m_worker_error) {
// set back the original range and cancel callback
m_progress->set_range(m_range);
// Make sure progress indicators get the last value of their range
// to make sure they close, fade out, whathever
m_progress->set_progress(m_range);
m_progress->set_cancel_callback();
wxEndBusyCursor();
if (m_worker_error) {
m_finalized = true;
m_progress->set_status_text("");
m_progress->set_progress(m_range);
on_exception(m_worker_error);
}
else {
// This is an RAII solution to remember that finalization is
// running. The run method calls update_status(status_range(), "")
// at the end, which queues up a call to this handler in all cases.
// If process also calls update_status with maxed out status arg
// it will call this handler twice. It is not a problem unless
// yield is called inside the finilize() method, which would
// jump out of finalize and call this handler again.
struct Finalizing {
bool &flag;
Finalizing (bool &f): flag(f) { flag = true; }
~Finalizing() { flag = false; }
} fin(m_finalizing);
finalize();
}
// dont do finalization again for the same process
m_finalized = true;
}
}, m_thread_evt_id);
}
void GUI::Job::start()
{ // Start the job. No effect if the job is already running
if (!m_running.load()) {
prepare();
// Save the current status indicatior range and push the new one
m_range = m_progress->get_range();
m_progress->set_range(status_range());
// init cancellation flag and set the cancel callback
m_canceled.store(false);
m_progress->set_cancel_callback(
[this]() { m_canceled.store(true); });
m_finalized = false;
m_finalizing = false;
// Changing cursor to busy
wxBeginBusyCursor();
try { // Execute the job
m_worker_error = nullptr;
m_thread = create_thread([this] { this->run(m_worker_error); });
} catch (std::exception &) {
update_status(status_range(),
_(L("ERROR: not enough resources to "
"execute a new job.")));
}
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
}
}
bool GUI::Job::join(int timeout_ms)
{
if (!m_thread.joinable()) return true;
if (timeout_ms <= 0)
m_thread.join();
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
return false;
return true;
}
void GUI::ExclusiveJobGroup::start(size_t jid) {
assert(jid < m_jobs.size());
stop_all();
m_jobs[jid]->start();
}
void GUI::ExclusiveJobGroup::join_all(int wait_ms)
{
std::vector<bool> aborted(m_jobs.size(), false);
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
aborted[jid] = m_jobs[jid]->join(wait_ms);
if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; }))
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
}
bool GUI::ExclusiveJobGroup::is_any_running() const
{
return std::any_of(m_jobs.begin(), m_jobs.end(),
[](const std::unique_ptr<GUI::Job> &j) {
return j->is_running();
});
}
}

View file

@ -3,119 +3,53 @@
#include <atomic>
#include <exception>
#include <future>
#include "libslic3r/libslic3r.h"
#include <slic3r/GUI/I18N.hpp>
#include "ProgressIndicator.hpp"
#include <wx/event.h>
#include <boost/thread.hpp>
namespace Slic3r { namespace GUI {
// A class to handle UI jobs like arranging and optimizing rotation.
// These are not instant jobs, the user has to be informed about their
// state in the status progress indicator. On the other hand they are
// separated from the background slicing process. Ideally, these jobs should
// run when the background process is not running.
//
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class Job : public wxEvtHandler
{
int m_range = 100;
int m_thread_evt_id = wxID_ANY;
boost::thread m_thread;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false, m_finalizing = false;
std::shared_ptr<ProgressIndicator> m_progress;
std::exception_ptr m_worker_error = nullptr;
void run(std::exception_ptr &);
protected:
// status range for a particular job
virtual int status_range() const { return 100; }
// status update, to be used from the work thread (process() method)
void update_status(int st, const wxString &msg = "");
// A class representing a job that is to be run in the background, not blocking
// the main thread. Running it is up to a Worker object (see Worker interface)
class Job {
public:
bool was_canceled() const { return m_canceled.load(); }
// A controller interface that informs the job about cancellation and
// makes it possible for the job to advertise its status.
class Ctl {
public:
virtual ~Ctl() = default;
// Launched just before start(), a job can use it to prepare internals
virtual void prepare() {}
// status update, to be used from the work thread (process() method)
virtual void update_status(int st, const std::string &msg = "") = 0;
// The method where the actual work of the job should be defined.
virtual void process() = 0;
// Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize() { m_finalized = true; }
// Returns true if the job was asked to cancel itself.
virtual bool was_canceled() const = 0;
// Execute a functor on the main thread. Note that the exact time of
// execution is hard to determine. This can be used to make modifications
// on the UI, like displaying some intermediate results or modify the
// cursor.
// This function returns a std::future<void> object which enables the
// caller to optionally wait for the main thread to finish the function call.
virtual std::future<void> call_on_main_thread(std::function<void()> fn) = 0;
};
virtual ~Job() = default;
// The method where the actual work of the job should be defined. This is
// run on the worker thread.
virtual void process(Ctl &ctl) = 0;
// Launched when the job is finished on the UI thread.
// If the job was cancelled, the first parameter will have a true value.
// Exceptions occuring in process() are redirected from the worker thread
// into the main (UI) thread. This method is called from the main thread and
// can be overriden to handle these exceptions.
virtual void on_exception(const std::exception_ptr &eptr)
{
if (eptr) std::rethrow_exception(eptr);
}
public:
Job(std::shared_ptr<ProgressIndicator> pri);
bool is_finalized() const { return m_finalized; }
Job(const Job &) = delete;
Job(Job &&) = delete;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = delete;
void start();
// To wait for the running job and join the threads. False is
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
bool join(int timeout_ms = 0);
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
};
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started.
class ExclusiveJobGroup
{
static const int ABORT_WAIT_MAX_MS = 10000;
std::vector<std::unique_ptr<GUI::Job>> m_jobs;
protected:
virtual void before_start() {}
public:
virtual ~ExclusiveJobGroup() = default;
size_t add_job(std::unique_ptr<GUI::Job> &&job)
{
m_jobs.emplace_back(std::move(job));
return m_jobs.size() - 1;
}
void start(size_t jid);
void cancel_all() { for (auto& j : m_jobs) j->cancel(); }
void join_all(int wait_ms = 0);
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
bool is_any_running() const;
// into the main (UI) thread. This method receives the exception and can
// handle it properly. Assign nullptr to this second argument before
// function return to prevent further action. Leaving it with a non-null
// value will result in rethrowing by the worker.
virtual void finalize(bool /*canceled*/, std::exception_ptr &) {}
};
}} // namespace Slic3r::GUI

View file

@ -12,11 +12,15 @@ void NotificationProgressIndicator::set_range(int range)
void NotificationProgressIndicator::set_cancel_callback(CancelFn fn)
{
m_nm->progress_indicator_set_cancel_callback(std::move(fn));
m_cancelfn = std::move(fn);
m_nm->progress_indicator_set_cancel_callback(m_cancelfn);
}
void NotificationProgressIndicator::set_progress(int pr)
{
if (!pr)
set_cancel_callback(m_cancelfn);
m_nm->progress_indicator_set_progress(pr);
}

View file

@ -9,7 +9,7 @@ class NotificationManager;
class NotificationProgressIndicator: public ProgressIndicator {
NotificationManager *m_nm = nullptr;
CancelFn m_cancelfn;
public:
explicit NotificationProgressIndicator(NotificationManager *nm);

View file

@ -1,17 +0,0 @@
#include "PlaterJob.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
namespace Slic3r { namespace GUI {
void PlaterJob::on_exception(const std::exception_ptr &eptr)
{
try {
if (eptr)
std::rethrow_exception(eptr);
} catch (std::exception &e) {
show_error(m_plater, _(L("An unexpected error occured")) + ": "+ e.what());
}
}
}} // namespace Slic3r::GUI

View file

@ -1,24 +0,0 @@
#ifndef PLATERJOB_HPP
#define PLATERJOB_HPP
#include "Job.hpp"
namespace Slic3r { namespace GUI {
class Plater;
class PlaterJob : public Job {
protected:
Plater *m_plater;
void on_exception(const std::exception_ptr &) override;
public:
PlaterJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater):
Job{std::move(pri)}, m_plater{plater} {}
};
}} // namespace Slic3r::GUI
#endif // PLATERJOB_HPP

View file

@ -0,0 +1,127 @@
#ifndef PLATERWORKER_HPP
#define PLATERWORKER_HPP
#include <map>
#include "Worker.hpp"
#include "BusyCursorJob.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
namespace Slic3r { namespace GUI {
class Plater;
template<class WorkerSubclass>
class PlaterWorker: public Worker {
WorkerSubclass m_w;
Plater *m_plater;
class PlaterJob : public Job {
std::unique_ptr<Job> m_job;
Plater *m_plater;
public:
void process(Ctl &c) override
{
// Ensure that wxWidgets processing wakes up to handle outgoing
// messages in plater's wxIdle handler. Otherwise it might happen
// that the message will only be processed when an event like mouse
// move comes along which might be too late.
struct WakeUpCtl: Ctl {
Ctl &ctl;
WakeUpCtl(Ctl &c) : ctl{c} {}
void update_status(int st, const std::string &msg = "") override
{
wxWakeUpIdle();
ctl.update_status(st, msg);
}
bool was_canceled() const override { return ctl.was_canceled(); }
std::future<void> call_on_main_thread(std::function<void()> fn) override
{
wxWakeUpIdle();
return ctl.call_on_main_thread(std::move(fn));
}
} wctl{c};
CursorSetterRAII busycursor{wctl};
m_job->process(wctl);
}
void finalize(bool canceled, std::exception_ptr &eptr) override
{
m_job->finalize(canceled, eptr);
if (eptr) try {
std::rethrow_exception(eptr);
} catch (std::exception &e) {
show_error(m_plater, _L("An unexpected error occured: ") + e.what());
eptr = nullptr;
}
}
PlaterJob(Plater *p, std::unique_ptr<Job> j)
: m_job{std::move(j)}, m_plater{p}
{
// TODO: decide if disabling slice button during UI job is what we
// want.
// if (m_plater)
// m_plater->sidebar().enable_buttons(false);
}
~PlaterJob() override
{
// TODO: decide if disabling slice button during UI job is what we want.
// Reload scene ensures that the slice button gets properly
// enabled or disabled after the job finishes, depending on the
// state of slicing. This might be an overkill but works for now.
// if (m_plater)
// m_plater->canvas3D()->reload_scene(false);
}
};
public:
template<class... WorkerArgs>
PlaterWorker(Plater *plater, WorkerArgs &&...args)
: m_w{std::forward<WorkerArgs>(args)...}, m_plater{plater}
{
// Ensure that messages from the worker thread to the UI thread are
// processed continuously.
plater->Bind(wxEVT_IDLE, [this](wxIdleEvent &) {
process_events();
});
}
// Always package the job argument into a PlaterJob
bool push(std::unique_ptr<Job> job) override
{
return m_w.push(std::make_unique<PlaterJob>(m_plater, std::move(job)));
}
bool is_idle() const override { return m_w.is_idle(); }
void cancel() override { m_w.cancel(); }
void cancel_all() override { m_w.cancel_all(); }
void process_events() override { m_w.process_events(); }
bool wait_for_current_job(unsigned timeout_ms = 0) override
{
return m_w.wait_for_current_job(timeout_ms);
}
bool wait_for_idle(unsigned timeout_ms = 0) override
{
return m_w.wait_for_idle(timeout_ms);
}
};
}} // namespace Slic3r::GUI
#endif // PLATERJOB_HPP

View file

@ -12,6 +12,8 @@
#include "slic3r/GUI/GUI_App.hpp"
#include "libslic3r/AppConfig.hpp"
#include <slic3r/GUI/I18N.hpp>
namespace Slic3r { namespace GUI {
void RotoptimizeJob::prepare()
@ -45,20 +47,23 @@ void RotoptimizeJob::prepare()
}
}
void RotoptimizeJob::process()
void RotoptimizeJob::process(Ctl &ctl)
{
int prev_status = 0;
auto statustxt = _u8L("Searching for optimal orientation");
ctl.update_status(0, statustxt);
auto params =
sla::RotOptimizeParams{}
.accuracy(m_accuracy)
.print_config(&m_default_print_cfg)
.statucb([this, &prev_status](int s)
.statucb([this, &prev_status, &ctl, &statustxt](int s)
{
if (s > 0 && s < 100)
update_status(prev_status + s / m_selected_object_ids.size(),
_(L("Searching for optimal orientation")));
ctl.update_status(prev_status + s / m_selected_object_ids.size(),
statustxt);
return !was_canceled();
return !ctl.was_canceled();
});
@ -71,16 +76,20 @@ void RotoptimizeJob::process()
prev_status += 100 / m_selected_object_ids.size();
if (was_canceled()) break;
if (ctl.was_canceled()) break;
}
update_status(100, was_canceled() ? _(L("Orientation search canceled.")) :
_(L("Orientation found.")));
ctl.update_status(100, ctl.was_canceled() ?
_u8L("Orientation search canceled.") :
_u8L("Orientation found."));
}
void RotoptimizeJob::finalize()
RotoptimizeJob::RotoptimizeJob() : m_plater{wxGetApp().plater()} { prepare(); }
void RotoptimizeJob::finalize(bool canceled, std::exception_ptr &eptr)
{
if (was_canceled()) return;
if (canceled || eptr)
return;
for (const ObjRot &objrot : m_selected_object_ids) {
ModelObject *o = m_plater->model().objects[size_t(objrot.idx)];
@ -111,10 +120,8 @@ void RotoptimizeJob::finalize()
// m_plater->find_new_position(o->instances);
}
if (!was_canceled())
if (!canceled)
m_plater->update();
Job::finalize();
}
}}

View file

@ -1,16 +1,17 @@
#ifndef ROTOPTIMIZEJOB_HPP
#define ROTOPTIMIZEJOB_HPP
#include "PlaterJob.hpp"
#include "Job.hpp"
#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/GUI/I18N.hpp"
namespace Slic3r {
namespace Slic3r { namespace GUI {
namespace GUI {
class Plater;
class RotoptimizeJob : public PlaterJob
class RotoptimizeJob : public Job
{
using FindFn = std::function<Vec2d(const ModelObject & mo,
const sla::RotOptimizeParams &params)>;
@ -44,19 +45,16 @@ class RotoptimizeJob : public PlaterJob
};
std::vector<ObjRot> m_selected_object_ids;
protected:
void prepare() override;
void process() override;
Plater *m_plater;
public:
RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}
{}
void prepare();
void process(Ctl &ctl) override;
void finalize() override;
RotoptimizeJob();
void finalize(bool canceled, std::exception_ptr &) override;
static constexpr size_t get_methods_count() { return std::size(Methods); }

View file

@ -0,0 +1,114 @@
#ifndef SLAIMPORTDIALOG_HPP
#define SLAIMPORTDIALOG_HPP
#include "SLAImportJob.hpp"
#include <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/filename.h>
#include <wx/filepicker.h>
#include "libslic3r/AppConfig.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
//#include "libslic3r/Model.hpp"
//#include "libslic3r/PresetBundle.hpp"
namespace Slic3r { namespace GUI {
class SLAImportDialog: public wxDialog, public SLAImportJobView {
wxFilePickerCtrl *m_filepicker;
wxComboBox *m_import_dropdown, *m_quality_dropdown;
public:
SLAImportDialog(Plater *plater)
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
{
auto szvert = new wxBoxSizer{wxVERTICAL};
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
szfilepck->Add(m_filepicker, 1);
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
static const std::vector<wxString> inp_choices = {
_(L("Import model and profile")),
_(L("Import profile only")),
_(L("Import model only"))
};
m_import_dropdown = new wxComboBox(
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_import_dropdown);
szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5);
static const std::vector<wxString> qual_choices = {
_(L("Accurate")),
_(L("Balanced")),
_(L("Quick"))
};
m_quality_dropdown = new wxComboBox(
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_quality_dropdown);
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
if (get_selection() == Sel::profileOnly)
m_quality_dropdown->Disable();
else m_quality_dropdown->Enable();
});
szvert->Add(szchoices, 0, wxALL, 5);
szvert->AddStretchSpacer(1);
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
szbtn->Add(new wxButton{this, wxID_CANCEL});
szbtn->Add(new wxButton{this, wxID_OK});
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
SetSizerAndFit(szvert);
}
Sel get_selection() const override
{
int sel = m_import_dropdown->GetSelection();
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
}
Vec2i get_marchsq_windowsize() const override
{
enum { Accurate, Balanced, Fast};
switch(m_quality_dropdown->GetSelection())
{
case Fast: return {8, 8};
case Balanced: return {4, 4};
default:
case Accurate:
return {2, 2};
}
}
std::string get_path() const override
{
return m_filepicker->GetPath().ToUTF8().data();
}
};
}} // namespace Slic3r::GUI
#endif // SLAIMPORTDIALOG_HPP

View file

@ -3,7 +3,6 @@
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
@ -11,104 +10,10 @@
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
#include <wx/dialog.h>
#include <wx/stattext.h>
#include <wx/combobox.h>
#include <wx/filename.h>
#include <wx/filepicker.h>
namespace Slic3r { namespace GUI {
enum class Sel { modelAndProfile, profileOnly, modelOnly};
class ImportDlg: public wxDialog {
wxFilePickerCtrl *m_filepicker;
wxComboBox *m_import_dropdown, *m_quality_dropdown;
public:
ImportDlg(Plater *plater)
: wxDialog{plater, wxID_ANY, "Import SLA archive"}
{
auto szvert = new wxBoxSizer{wxVERTICAL};
auto szfilepck = new wxBoxSizer{wxHORIZONTAL};
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
szfilepck->Add(m_filepicker, 1);
szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5);
auto szchoices = new wxBoxSizer{wxHORIZONTAL};
static const std::vector<wxString> inp_choices = {
_(L("Import model and profile")),
_(L("Import profile only")),
_(L("Import model only"))
};
m_import_dropdown = new wxComboBox(
this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize,
inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_import_dropdown);
szchoices->Add(new wxStaticText(this, wxID_ANY, _L("Quality") + ": "), 0, wxALIGN_CENTER | wxALL, 5);
static const std::vector<wxString> qual_choices = {
_(L("Accurate")),
_(L("Balanced")),
_(L("Quick"))
};
m_quality_dropdown = new wxComboBox(
this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize,
qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN);
szchoices->Add(m_quality_dropdown);
m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
if (get_selection() == Sel::profileOnly)
m_quality_dropdown->Disable();
else m_quality_dropdown->Enable();
});
szvert->Add(szchoices, 0, wxALL, 5);
szvert->AddStretchSpacer(1);
auto szbtn = new wxBoxSizer(wxHORIZONTAL);
szbtn->Add(new wxButton{this, wxID_CANCEL});
szbtn->Add(new wxButton{this, wxID_OK});
szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5);
SetSizerAndFit(szvert);
}
Sel get_selection() const
{
int sel = m_import_dropdown->GetSelection();
return Sel(std::min(int(Sel::modelOnly), std::max(0, sel)));
}
Vec2i get_marchsq_windowsize() const
{
enum { Accurate, Balanced, Fast};
switch(m_quality_dropdown->GetSelection())
{
case Fast: return {8, 8};
case Balanced: return {4, 4};
default:
case Accurate:
return {2, 2};
}
}
wxString get_path() const
{
return m_filepicker->GetPath();
}
};
class SLAImportJob::priv {
public:
Plater *plater;
@ -122,23 +27,28 @@ public:
std::string err;
ConfigSubstitutions config_substitutions;
ImportDlg import_dlg;
const SLAImportJobView * import_dlg;
priv(Plater *plt) : plater{plt}, import_dlg{plt} {}
priv(Plater *plt, const SLAImportJobView *view) : plater{plt}, import_dlg{view} {}
};
SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater)
: PlaterJob{std::move(pri), plater}, p{std::make_unique<priv>(plater)}
{}
SLAImportJob::SLAImportJob(const SLAImportJobView *view)
: p{std::make_unique<priv>(wxGetApp().plater(), view)}
{
prepare();
}
SLAImportJob::~SLAImportJob() = default;
void SLAImportJob::process()
void SLAImportJob::process(Ctl &ctl)
{
auto progr = [this](int s) {
auto statustxt = _u8L("Importing SLA archive");
ctl.update_status(0, statustxt);
auto progr = [&ctl, &statustxt](int s) {
if (s < 100)
update_status(int(s), _(L("Importing SLA archive")));
return !was_canceled();
ctl.update_status(int(s), statustxt);
return !ctl.was_canceled();
};
if (p->path.empty()) return;
@ -161,15 +71,15 @@ void SLAImportJob::process()
p->err = ex.what();
}
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
_(L("Importing done.")));
ctl.update_status(100, ctl.was_canceled() ? _u8L("Importing canceled.") :
_u8L("Importing done."));
}
void SLAImportJob::reset()
{
p->sel = Sel::modelAndProfile;
p->mesh = {};
p->profile = m_plater->sla_print().full_print_config();
p->profile = p->plater->sla_print().full_print_config();
p->win = {2, 2};
p->path.Clear();
}
@ -178,22 +88,19 @@ void SLAImportJob::prepare()
{
reset();
if (p->import_dlg.ShowModal() == wxID_OK) {
auto path = p->import_dlg.get_path();
auto nm = wxFileName(path);
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
p->sel = p->import_dlg.get_selection();
p->win = p->import_dlg.get_marchsq_windowsize();
p->config_substitutions.clear();
} else {
p->path = "";
}
auto path = p->import_dlg->get_path();
auto nm = wxFileName(path);
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
p->sel = p->import_dlg->get_selection();
p->win = p->import_dlg->get_marchsq_windowsize();
p->config_substitutions.clear();
}
void SLAImportJob::finalize()
void SLAImportJob::finalize(bool canceled, std::exception_ptr &eptr)
{
// Ignore the arrange result if aborted.
if (was_canceled()) return;
if (canceled || eptr)
return;
if (!p->err.empty()) {
show_error(p->plater, p->err);
@ -204,7 +111,7 @@ void SLAImportJob::finalize()
std::string name = wxFileName(p->path).GetName().ToUTF8().data();
if (p->profile.empty()) {
m_plater->get_notification_manager()->push_notification(
p->plater->get_notification_manager()->push_notification(
NotificationType::CustomNotification,
NotificationManager::NotificationLevel::WarningNotificationLevel,
_L("The imported SLA archive did not contain any presets. "
@ -213,7 +120,7 @@ void SLAImportJob::finalize()
if (p->sel != Sel::modelOnly) {
if (p->profile.empty())
p->profile = m_plater->sla_print().full_print_config();
p->profile = p->plater->sla_print().full_print_config();
const ModelObjectPtrs& objects = p->plater->model().objects;
for (auto object : objects)

View file

@ -1,22 +1,37 @@
#ifndef SLAIMPORTJOB_HPP
#define SLAIMPORTJOB_HPP
#include "PlaterJob.hpp"
#include "Job.hpp"
#include "libslic3r/Point.hpp"
namespace Slic3r { namespace GUI {
class SLAImportJob : public PlaterJob {
class SLAImportJobView {
public:
enum Sel { modelAndProfile, profileOnly, modelOnly};
virtual ~SLAImportJobView() = default;
virtual Sel get_selection() const = 0;
virtual Vec2i get_marchsq_windowsize() const = 0;
virtual std::string get_path() const = 0;
};
class Plater;
class SLAImportJob : public Job {
class priv;
std::unique_ptr<priv> p;
protected:
void prepare() override;
void process() override;
void finalize() override;
using Sel = SLAImportJobView::Sel;
public:
SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater);
void prepare();
void process(Ctl &ctl) override;
void finalize(bool canceled, std::exception_ptr &) override;
SLAImportJob(const SLAImportJobView *);
~SLAImportJob();
void reset();

View file

@ -0,0 +1,123 @@
#ifndef THREADSAFEQUEUE_HPP
#define THREADSAFEQUEUE_HPP
#include <type_traits>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <atomic>
namespace Slic3r { namespace GUI {
// Helper structure for overloads of ThreadSafeQueueSPSC::consume_one()
// to block if the queue is empty.
struct BlockingWait
{
// Timeout to wait for the arrival of new element into the queue.
unsigned timeout_ms = 0;
// An optional atomic flag to set true if an incoming element gets
// consumed. The flag will be atomically set to true when popping the
// front of the queue.
std::atomic<bool> *pop_flag = nullptr;
};
// A thread safe queue for one producer and one consumer.
template<class T,
template<class, class...> class Container = std::deque,
class... ContainerArgs>
class ThreadSafeQueueSPSC
{
std::queue<T, Container<T, ContainerArgs...>> m_queue;
mutable std::mutex m_mutex;
std::condition_variable m_cond_var;
public:
// Consume one element, block if the queue is empty.
template<class Fn> bool consume_one(const BlockingWait &blkw, Fn &&fn)
{
static_assert(!std::is_reference_v<T>, "");
static_assert(std::is_default_constructible_v<T>, "");
static_assert(std::is_move_assignable_v<T> || std::is_copy_assignable_v<T>, "");
T el;
{
std::unique_lock lk{m_mutex};
auto pred = [this]{ return !m_queue.empty(); };
if (blkw.timeout_ms > 0) {
auto timeout = std::chrono::milliseconds(blkw.timeout_ms);
if (!m_cond_var.wait_for(lk, timeout, pred))
return false;
}
else
m_cond_var.wait(lk, pred);
if constexpr (std::is_move_assignable_v<T>)
el = std::move(m_queue.front());
else
el = m_queue.front();
m_queue.pop();
if (blkw.pop_flag)
// The optional flag is set before the lock us unlocked.
blkw.pop_flag->store(true);
}
fn(el);
return true;
}
// Consume one element, return true if consumed, false if queue was empty.
template<class Fn> bool consume_one(Fn &&fn)
{
T el;
{
std::unique_lock lk{m_mutex};
if (!m_queue.empty()) {
if constexpr (std::is_move_assignable_v<T>)
el = std::move(m_queue.front());
else
el = m_queue.front();
m_queue.pop();
} else
return false;
}
fn(el);
return true;
}
// Push element into the queue.
template<class...TArgs> void push(TArgs&&...el)
{
std::lock_guard lk{m_mutex};
m_queue.emplace(std::forward<TArgs>(el)...);
m_cond_var.notify_one();
}
bool empty() const
{
std::lock_guard lk{m_mutex};
return m_queue.empty();
}
size_t size() const
{
std::lock_guard lk{m_mutex};
return m_queue.size();
}
void clear()
{
std::lock_guard lk{m_mutex};
while (!m_queue.empty()) m_queue.pop();
}
};
}} // namespace Slic3r::GUI
#endif // THREADSAFEQUEUE_HPP

View file

@ -0,0 +1,119 @@
#ifndef PRUSALSICER_WORKER_HPP
#define PRUSALSICER_WORKER_HPP
#include <memory>
#include "Job.hpp"
namespace Slic3r { namespace GUI {
// An interface of a worker that runs jobs on a dedicated worker thread, one
// after the other. It is assumed that every method of this class is called
// from the same main thread.
class Worker {
public:
// Queue up a new job after the current one. This call does not block.
// Returns false if the job gets discarded.
virtual bool push(std::unique_ptr<Job> job) = 0;
// Returns true if no job is running, the job queue is empty and no job
// message is left to be processed. This means that nothing is left to
// finalize or take care of in the main thread.
virtual bool is_idle() const = 0;
// Ask the current job gracefully to cancel. This call is not blocking and
// the job may or may not cancel eventually, depending on its
// implementation. Note that it is not trivial to kill a thread forcefully
// and we don't need that.
virtual void cancel() = 0;
// This method will delete the queued jobs and cancel the current one.
virtual void cancel_all() = 0;
// Needs to be called continuously to process events (like status update
// or finalizing of jobs) in the main thread. This can be done e.g. in a
// wxIdle handler.
virtual void process_events() = 0;
// Wait until the current job finishes. Timeout will only be considered
// if not zero. Returns false if timeout is reached but the job has not
// finished.
virtual bool wait_for_current_job(unsigned timeout_ms = 0) = 0;
// Wait until the whole job queue finishes. Timeout will only be considered
// if not zero. Returns false only if timeout is reached but the worker has
// not reached the idle state.
virtual bool wait_for_idle(unsigned timeout_ms = 0) = 0;
// The destructor shall properly close the worker thread.
virtual ~Worker() = default;
};
template<class Fn> constexpr bool IsProcessFn = std::is_invocable_v<Fn, Job::Ctl&>;
template<class Fn> constexpr bool IsFinishFn = std::is_invocable_v<Fn, bool, std::exception_ptr&>;
// Helper function to use the worker with arbitrary functors.
template<class ProcessFn, class FinishFn,
class = std::enable_if_t<IsProcessFn<ProcessFn>>,
class = std::enable_if_t<IsFinishFn<FinishFn>> >
bool queue_job(Worker &w, ProcessFn fn, FinishFn finishfn)
{
struct LambdaJob: Job {
ProcessFn fn;
FinishFn finishfn;
LambdaJob(ProcessFn pfn, FinishFn ffn)
: fn{std::move(pfn)}, finishfn{std::move(ffn)}
{}
void process(Ctl &ctl) override { fn(ctl); }
void finalize(bool canceled, std::exception_ptr &eptr) override
{
finishfn(canceled, eptr);
}
};
auto j = std::make_unique<LambdaJob>(std::move(fn), std::move(finishfn));
return w.push(std::move(j));
}
template<class ProcessFn, class = std::enable_if_t<IsProcessFn<ProcessFn>>>
bool queue_job(Worker &w, ProcessFn fn)
{
return queue_job(w, std::move(fn), [](bool, std::exception_ptr &) {});
}
inline bool queue_job(Worker &w, std::unique_ptr<Job> j)
{
return w.push(std::move(j));
}
// Replace the current job queue with a new job. The signature is the same
// as for queue_job(). This cancels all jobs and
// will not wait. The new job will begin after the queue cancels properly.
// Note that this can be called from the UI thread and will not block it if
// the jobs take longer to cancel.
template<class...Args> bool replace_job(Worker &w, Args&& ...args)
{
w.cancel_all();
return queue_job(w, std::forward<Args>(args)...);
}
// Cancel the current job and wait for it to actually be stopped.
inline bool stop_current_job(Worker &w, unsigned timeout_ms = 0)
{
w.cancel();
return w.wait_for_current_job(timeout_ms);
}
// Cancel all pending jobs including current one and wait until the worker
// becomes idle.
inline bool stop_queue(Worker &w, unsigned timeout_ms = 0)
{
w.cancel_all();
return w.wait_for_idle(timeout_ms);
}
}} // namespace Slic3r::GUI
#endif // WORKER_HPP

View file

@ -574,7 +574,7 @@ void MainFrame::shutdown()
#endif // _WIN32
if (m_plater != nullptr) {
m_plater->stop_jobs();
m_plater->get_ui_job_worker().cancel_all();
// Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC,
// when closing the application using Command+Q, a mouse event is triggered after this lambda is completed,
@ -1073,7 +1073,7 @@ static wxMenu* generate_help_menu()
append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"),
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"),
[](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); });
[](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases", nullptr, false); });
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
//# wxTheApp->check_version(1);
//# });
@ -1090,7 +1090,7 @@ static wxMenu* generate_help_menu()
append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
[](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME),
[](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/slic3r/issues/new"); });
[](wxCommandEvent&) { wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/slic3r/issues/new", nullptr, false); });
if (wxGetApp().is_editor())
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"),
[](wxCommandEvent&) { Slic3r::GUI::about(); });
@ -1211,7 +1211,7 @@ void MainFrame::init_menubar_as_editor()
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S Archive") + dots, _L("Load an SL1 / Sl1S archive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr && !m_plater->is_any_job_running(); }, this);
[this](){return m_plater != nullptr && m_plater->get_ui_job_worker().is_idle(); }, this);
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"),

View file

@ -18,6 +18,7 @@
#include "libslic3r/Color.hpp"
#endif // ENABLE_COLOR_CLASSES
#include "GUI.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include "ConfigWizard.hpp"
#include "wxExtensions.hpp"
@ -27,7 +28,6 @@
namespace Slic3r {
namespace GUI {
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, long style, wxBitmap bitmap)
: wxDialog(parent ? parent : dynamic_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, boldfont(wxGetApp().normal_font())
@ -139,11 +139,7 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
wxFont monospace = wxGetApp().code_font();
#ifdef _WIN32
wxColour text_clr = wxGetApp().get_label_clr_default();
#else
wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
#endif
wxColour bgr_clr = parent->GetBackgroundColour(); //wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
#if ENABLE_COLOR_CLASSES
auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
@ -194,13 +190,26 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
}
html->SetMinSize(page_size);
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
std::string msg_escaped = xml_escape(into_u8(msg), is_marked_msg);
boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>");
if (monospaced_font)
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
html->SetPage(format_wxstr("<html>"
"<body bgcolor=%1% link=%2%>"
"<font color=%2%>"
"%3%"
"</font>"
"</body>"
"</html>",
bgr_clr_str, text_clr_str, from_u8(msg_escaped)));
html->Bind(wxEVT_HTML_LINK_CLICKED, [parent](wxHtmlLinkEvent& event) {
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref(), parent, false);
event.Skip(false);
});
content_sizer->Add(html, 1, wxEXPAND);
wxGetApp().UpdateDarkUI(html);
}

View file

@ -301,7 +301,7 @@ void NotificationManager::PopNotification::count_lines()
float width_of_a = ImGui::CalcTextSize("a").x;
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) {
letter_count++;
letter_count += get_utf8_sequence_length(text, last_end + letter_count);
}
m_endlines.push_back(last_end + letter_count);
last_end += letter_count;

View file

@ -699,12 +699,7 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_text(wxDC& dc, wxPoint pos, const wxStr
#else
dc.SetFont(old_font.Bold().Underlined());
#endif
dc.SetTextForeground(color ? *color :
#ifdef _WIN32
wxGetApp().get_label_clr_default());
#else
wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
#endif /* _WIN32 */
dc.SetTextForeground(color ? *color : wxGetApp().get_label_clr_default());
dc.DrawText(out_text, pos);
dc.SetTextForeground(old_clr);
dc.SetFont(old_font);

View file

@ -977,36 +977,7 @@ wxString OptionsGroup::get_url(const std::string& path_end)
bool OptionsGroup::launch_browser(const std::string& path_end)
{
bool launch = true;
if (get_app_config()->get("suppress_hyperlinks").empty()) {
wxWindow* parent = wxGetApp().mainframe->m_tabpanel;
RichMessageDialog dialog(parent, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO);
dialog.ShowCheckBox(_L("Remember my choice"));
int answer = dialog.ShowModal();
if (answer == wxID_CANCEL)
return false;
if (dialog.IsCheckBoxChecked()) {
wxString preferences_item = _L("Suppress to open hyperlink in browser");
wxString msg =
_L("PrusaSlicer will remember your choice.") + "\n\n" +
_L("You will not be asked about it again on label hovering.") + "\n\n" +
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
MessageDialog msg_dlg(parent, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
if (msg_dlg.ShowModal() == wxID_CANCEL)
return false;
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
}
launch = answer == wxID_YES;
}
if (launch)
launch = get_app_config()->get("suppress_hyperlinks") != "1";
return launch && wxLaunchDefaultBrowser(OptionsGroup::get_url(path_end));
return wxGetApp().open_browser_with_warning_dialog(OptionsGroup::get_url(path_end), wxGetApp().mainframe->m_tabpanel);
}

View file

@ -73,7 +73,10 @@
#include "Jobs/FillBedJob.hpp"
#include "Jobs/RotoptimizeJob.hpp"
#include "Jobs/SLAImportJob.hpp"
#include "Jobs/SLAImportDialog.hpp"
#include "Jobs/NotificationProgressIndicator.hpp"
#include "Jobs/PlaterWorker.hpp"
#include "Jobs/BoostThreadWorker.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "PrintHostDialogs.hpp"
#include "ConfigWizard.hpp"
@ -251,9 +254,9 @@ void ObjectInfo::update_warning_icon(const std::string& warning_icon_name)
enum SlicedInfoIdx
{
siFilament_g,
siFilament_m,
siFilament_mm3,
siFilament_g,
siMateril_unit,
siCost,
siEstimatedTime,
@ -293,9 +296,9 @@ SlicedInfo::SlicedInfo(wxWindow *parent) :
info_vec.push_back(std::pair<wxStaticText*, wxStaticText*>(text, info_label));
};
init_info_label(_L("Used Filament (g)"));
init_info_label(_L("Used Filament (m)"));
init_info_label(_L("Used Filament (mm³)"));
init_info_label(_L("Used Filament (g)"));
init_info_label(_L("Used Material (unit)"));
init_info_label(_L("Cost (money)"));
init_info_label(_L("Estimated printing time"));
@ -1638,54 +1641,12 @@ struct Plater::priv
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started. It is up the the plater to ensure that the background slicing
// can't be restarted while a ui job is still running.
class Jobs: public ExclusiveJobGroup
{
priv *m;
size_t m_arrange_id, m_fill_bed_id, m_rotoptimize_id, m_sla_import_id;
std::shared_ptr<NotificationProgressIndicator> m_pri;
void before_start() override { m->background_process.stop(); }
public:
Jobs(priv *_m) :
m(_m),
m_pri{std::make_shared<NotificationProgressIndicator>(m->notification_manager.get())}
{
m_arrange_id = add_job(std::make_unique<ArrangeJob>(m_pri, m->q));
m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m_pri, m->q));
m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m_pri, m->q));
m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m_pri, m->q));
}
void arrange()
{
m->take_snapshot(_L("Arrange"));
start(m_arrange_id);
}
void fill_bed()
{
m->take_snapshot(_L("Fill bed"));
start(m_fill_bed_id);
}
void optimize_rotation()
{
m->take_snapshot(_L("Optimize Rotation"));
start(m_rotoptimize_id);
}
void import_sla_arch()
{
m->take_snapshot(_L("Import SLA archive"));
start(m_sla_import_id);
}
} m_ui_jobs;
// TODO: A mechanism would be useful for blocking the plater interactions:
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
PlaterWorker<BoostThreadWorker> m_worker;
SLAImportDialog * m_sla_import_dlg;
bool delayed_scene_refresh;
std::string delayed_error_message;
@ -1720,8 +1681,33 @@ struct Plater::priv
fs::path output_file = get_export_file_path(FT_3MF);
suggested_project_name = output_file.empty() ? _L("Untitled") : from_u8(output_file.stem().string());
}
res = MessageDialog(mainframe, reason + "\n" + format_wxstr(_L("Do you want to save the changes to \"%1%\"?"), suggested_project_name),
wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL).ShowModal();
std::string act_key = "default_action_on_dirty_project";
std::string act = wxGetApp().app_config->get(act_key);
if (act.empty()) {
RichMessageDialog dialog(mainframe, reason + "\n" + format_wxstr(_L("Do you want to save the changes to \"%1%\"?"), suggested_project_name), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
dialog.ShowCheckBox(_L("Remember my choice"));
res = dialog.ShowModal();
if (res != wxID_CANCEL)
if (dialog.IsCheckBoxChecked()) {
wxString preferences_item = _L("Ask for unsaved changes in project");
wxString msg =
_L("PrusaSlicer will remember your choice.") + "\n\n" +
_L("You will not be asked about it again, when: \n"
"- Closing PrusaSlicer,\n"
"- Loading or creating a new project") + "\n\n" +
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
MessageDialog msg_dlg(mainframe, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
if (msg_dlg.ShowModal() == wxID_CANCEL)
return wxID_CANCEL;
get_app_config()->set(act_key, res == wxID_YES ? "1" : "0");
}
}
else
res = (act == "1") ? wxID_YES : wxID_NO;
if (res == wxID_YES)
if (!mainframe->save_project_as(project_name))
res = wxID_CANCEL;
@ -1916,9 +1902,7 @@ struct Plater::priv
bool can_reload_from_disk() const;
bool can_replace_with_stl() const;
bool can_split(bool to_objects) const;
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
bool can_scale_to_print_volume() const;
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type);
ThumbnailsList generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type);
@ -1990,7 +1974,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
}))
, sidebar(new Sidebar(q))
, notification_manager(std::make_unique<NotificationManager>(q))
, m_ui_jobs(this)
, m_worker{q, std::make_unique<NotificationProgressIndicator>(notification_manager.get()), "ui_worker"}
, m_sla_import_dlg{new SLAImportDialog{q}}
, delayed_scene_refresh(false)
, view_toolbar(GLToolbar::Radio, "View")
, collapse_toolbar(GLToolbar::Normal, "Collapse")
@ -2926,7 +2911,7 @@ void Plater::priv::remove(size_t obj_idx)
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
m_ui_jobs.cancel_all();
m_worker.cancel_all();
model.delete_object(obj_idx);
update();
// Delete object from Sidebar list. Do it after update, so that the GLScene selection is updated with the modified model.
@ -2941,7 +2926,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
if (! model.objects[obj_idx]->name.empty())
snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
Plater::TakeSnapshot snapshot(q, snapshot_label);
m_ui_jobs.cancel_all();
m_worker.cancel_all();
model.delete_object(obj_idx);
update();
object_list_changed();
@ -2959,7 +2944,7 @@ void Plater::priv::delete_all_objects_from_model()
view3D->get_canvas3d()->reset_sequential_print_clearance();
m_ui_jobs.cancel_all();
m_worker.cancel_all();
// Stop and reset the Print content.
background_process.reset();
@ -2991,7 +2976,7 @@ void Plater::priv::reset()
view3D->get_canvas3d()->reset_sequential_print_clearance();
m_ui_jobs.cancel_all();
m_worker.cancel_all();
// Stop and reset the Print content.
this->background_process.reset();
@ -3092,11 +3077,7 @@ void Plater::priv::split_volume()
void Plater::priv::scale_selection_to_fit_print_volume()
{
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
this->view3D->get_canvas3d()->get_selection().scale_to_fit_print_volume(this->bed.build_volume());
#else
this->view3D->get_canvas3d()->get_selection().scale_to_fit_print_volume(*config);
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
}
void Plater::priv::schedule_background_process()
@ -3292,7 +3273,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
bool Plater::priv::restart_background_process(unsigned int state)
{
if (m_ui_jobs.is_any_running()) {
if (!m_worker.is_idle()) {
// Avoid a race condition
return false;
}
@ -3378,11 +3359,39 @@ void Plater::priv::update_sla_scene()
this->update_restart_background_process(true, true);
}
// class used to show a wxBusyCursor and a wxBusyInfo
// and hide them on demand
class Busy
{
wxWindow* m_parent{ nullptr };
std::unique_ptr<wxBusyCursor> m_cursor;
std::unique_ptr<wxBusyInfo> m_dlg;
public:
Busy(const wxString& message, wxWindow* parent = nullptr) {
m_parent = parent;
m_cursor = std::make_unique<wxBusyCursor>();
m_dlg = std::make_unique<wxBusyInfo>(message, m_parent);
}
~Busy() { reset(); }
void update(const wxString& message) {
// this is ugly but necessary because the call to wxBusyInfo::UpdateLabel() is not working [WX 3.1.4]
m_dlg = std::make_unique<wxBusyInfo>(message, m_parent);
// m_dlg->UpdateLabel(message);
}
void reset() {
m_cursor.reset();
m_dlg.reset();
}
};
bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const fs::path& new_path, const wxString& snapshot)
{
const std::string path = new_path.string();
wxBusyCursor wait;
wxBusyInfo info(_L("Replace from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas());
Busy busy(_L("Replace from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas());
Model new_model;
try {
@ -3392,8 +3401,10 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
model_object->ensure_on_bed();
}
}
catch (std::exception&) {
catch (std::exception& e) {
// error while loading
busy.reset();
GUI::show_error(q, e.what());
return false;
}
@ -3617,12 +3628,13 @@ void Plater::priv::reload_from_disk()
std::vector<wxString> fail_list;
Busy busy(_L("Reload from:"), q->get_current_canvas3D()->get_wxglcanvas());
// load one file at a time
for (size_t i = 0; i < input_paths.size(); ++i) {
const auto& path = input_paths[i].string();
wxBusyCursor wait;
wxBusyInfo info(_L("Reload from:") + " " + from_u8(path), q->get_current_canvas3D()->get_wxglcanvas());
busy.update(_L("Reload from:") + " " + from_u8(path));
Model new_model;
try
@ -3633,9 +3645,11 @@ void Plater::priv::reload_from_disk()
model_object->ensure_on_bed();
}
}
catch (std::exception&)
catch (std::exception& e)
{
// error while loading
busy.reset();
GUI::show_error(q, e.what());
return;
}
@ -3710,15 +3724,15 @@ void Plater::priv::reload_from_disk()
}
}
busy.reset();
for (size_t i = 0; i < replace_paths.size(); ++i) {
const auto& path = replace_paths[i].string();
for (const SelectedVolume& sel_v : selected_volumes) {
ModelObject* old_model_object = model.objects[sel_v.object_idx];
ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx];
bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string());
if (!replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, "")) {
fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name));
}
// ModelObject* old_model_object = model.objects[sel_v.object_idx];
// ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx];
// bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string());
replace_volume_with_stl(sel_v.object_idx, sel_v.volume_idx, path, "");
}
}
@ -3920,7 +3934,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
{
if (evt.status.percent >= -1) {
if (m_ui_jobs.is_any_running()) {
if (!m_worker.is_idle()) {
// Avoid a race condition
return;
}
@ -4478,13 +4492,11 @@ bool Plater::priv::can_split(bool to_objects) const
return sidebar->obj_list()->is_splittable(to_objects);
}
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
bool Plater::priv::can_scale_to_print_volume() const
{
const BuildVolume::Type type = this->bed.build_volume().type();
return !view3D->get_canvas3d()->get_selection().is_empty() && (type == BuildVolume::Type::Rectangle || type == BuildVolume::Type::Circle);
}
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
bool Plater::priv::layers_height_allowed() const
{
@ -4609,7 +4621,7 @@ bool Plater::priv::can_simplify() const
bool Plater::priv::can_increase_instances() const
{
if (m_ui_jobs.is_any_running()
if (!m_worker.is_idle()
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
return false;
@ -4619,7 +4631,7 @@ bool Plater::priv::can_increase_instances() const
bool Plater::priv::can_decrease_instances() const
{
if (m_ui_jobs.is_any_running()
if (!m_worker.is_idle()
|| q->canvas3D()->get_gizmos_manager().is_in_editing_mode())
return false;
@ -4639,7 +4651,7 @@ bool Plater::priv::can_split_to_volumes() const
bool Plater::priv::can_arrange() const
{
return !model.objects.empty() && !m_ui_jobs.is_any_running();
return !model.objects.empty() && m_worker.is_idle();
}
bool Plater::priv::can_layers_editing() const
@ -5093,8 +5105,11 @@ void Plater::add_model(bool imperial_units/* = false*/)
void Plater::import_sl1_archive()
{
if (!p->m_ui_jobs.is_any_running())
p->m_ui_jobs.import_sla_arch();
auto &w = get_ui_job_worker();
if (w.is_idle() && p->m_sla_import_dlg->ShowModal() == wxID_OK) {
p->take_snapshot(_L("Import SLA archive"));
replace_job(w, std::make_unique<SLAImportJob>(p->m_sla_import_dlg));
}
}
void Plater::extract_config_from_project()
@ -5376,12 +5391,9 @@ bool Plater::load_files(const wxArrayString& filenames)
void Plater::update() { p->update(); }
void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); }
Worker &Plater::get_ui_job_worker() { return p->m_worker; }
bool Plater::is_any_job_running() const
{
return p->m_ui_jobs.is_any_running();
}
const Worker &Plater::get_ui_job_worker() const { return p->m_worker; }
void Plater::update_ui_from_settings() { p->update_ui_from_settings(); }
@ -5422,7 +5434,7 @@ void Plater::remove_selected()
return;
Plater::TakeSnapshot snapshot(this, _L("Delete Selected Objects"));
p->m_ui_jobs.cancel_all();
get_ui_job_worker().cancel_all();
p->view3D->delete_selected();
}
@ -5531,8 +5543,11 @@ void Plater::set_number_of_copies(/*size_t num*/)
void Plater::fill_bed_with_instances()
{
if (!p->m_ui_jobs.is_any_running())
p->m_ui_jobs.fill_bed();
auto &w = get_ui_job_worker();
if (w.is_idle()) {
p->take_snapshot(_L("Fill bed"));
replace_job(w, std::make_unique<FillBedJob>());
}
}
bool Plater::is_selection_empty() const
@ -5735,13 +5750,15 @@ void Plater::export_stl(bool extended, bool selection_only)
if (selection_only) {
const ModelObject* model_object = p->model.objects[obj_idx];
if (selection.get_mode() == Selection::Instance)
mesh = selection.is_single_full_object() ? mesh_to_export(*model_object, -1) : mesh_to_export(*model_object, selection.get_instance_idx());
mesh = mesh_to_export(*model_object, (selection.is_single_full_object() && model_object->instances.size() > 1) ? -1 : selection.get_instance_idx());
else {
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
mesh = model_object->volumes[volume->volume_idx()]->mesh();
mesh.transform(volume->get_volume_transformation().get_matrix(), true);
mesh.translate(-model_object->origin_translation.cast<float>());
}
if (!selection.is_single_full_object() || model_object->instances.size() == 1)
mesh.translate(-model_object->origin_translation.cast<float>());
}
else {
for (const ModelObject* o : p->model.objects) {
@ -5933,8 +5950,14 @@ void Plater::reslice()
if (canvas3D()->get_gizmos_manager().is_in_editing_mode(true))
return;
// Stop arrange and (or) optimize rotation tasks.
this->stop_jobs();
// Stop the running (and queued) UI jobs and only proceed if they actually
// get stopped.
unsigned timeout_ms = 10000;
if (!stop_queue(this->get_ui_job_worker(), timeout_ms)) {
BOOST_LOG_TRIVIAL(error) << "Could not stop UI job within "
<< timeout_ms << " milliseconds timeout!";
return;
}
if (printer_technology() == ptSLA) {
for (auto& object : model().objects)
@ -6398,8 +6421,11 @@ GLCanvas3D* Plater::get_current_canvas3D()
void Plater::arrange()
{
if (!p->m_ui_jobs.is_any_running())
p->m_ui_jobs.arrange();
auto &w = get_ui_job_worker();
if (w.is_idle()) {
p->take_snapshot(_L("Arrange"));
replace_job(w, std::make_unique<ArrangeJob>());
}
}
void Plater::set_current_canvas_as_dirty()
@ -6570,7 +6596,6 @@ void Plater::suppress_background_process(const bool stop_background_process)
void Plater::mirror(Axis axis) { p->mirror(axis); }
void Plater::split_object() { p->split_object(); }
void Plater::split_volume() { p->split_volume(); }
void Plater::optimize_rotation() { if (!p->m_ui_jobs.is_any_running()) p->m_ui_jobs.optimize_rotation(); }
void Plater::update_menus() { p->menus.update(); }
void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_action_buttons(ready_to_slice); }
@ -6806,9 +6831,7 @@ bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
bool Plater::can_replace_with_stl() const { return p->can_replace_with_stl(); }
bool Plater::can_mirror() const { return p->can_mirror(); }
bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); }
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
bool Plater::can_scale_to_print_volume() const { return p->can_scale_to_print_volume(); }
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); }

View file

@ -14,6 +14,7 @@
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "Jobs/Job.hpp"
#include "Jobs/Worker.hpp"
#include "Search.hpp"
class wxButton;
@ -177,8 +178,41 @@ public:
const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; }
void update();
void stop_jobs();
bool is_any_job_running() const;
// Get the worker handling the UI jobs (arrange, fill bed, etc...)
// Here is an example of starting up an ad-hoc job:
// queue_job(
// get_ui_job_worker(),
// [](Job::Ctl &ctl) {
// // Executed in the worker thread
//
// CursorSetterRAII cursor_setter{ctl};
// std::string msg = "Running";
//
// ctl.update_status(0, msg);
// for (int i = 0; i < 100; i++) {
// usleep(100000);
// if (ctl.was_canceled()) break;
// ctl.update_status(i + 1, msg);
// }
// ctl.update_status(100, msg);
// },
// [](bool, std::exception_ptr &e) {
// // Executed in UI thread after the work is done
//
// try {
// if (e) std::rethrow_exception(e);
// } catch (std::exception &e) {
// BOOST_LOG_TRIVIAL(error) << e.what();
// }
// e = nullptr;
// });
// This would result in quick run of the progress indicator notification
// from 0 to 100. Use replace_job() instead of queue_job() to cancel all
// pending jobs.
Worker& get_ui_job_worker();
const Worker & get_ui_job_worker() const;
void select_view(const std::string& direction);
void select_view_3D(const std::string& name);
@ -302,7 +336,6 @@ public:
void mirror(Axis axis);
void split_object();
void split_volume();
void optimize_rotation();
bool can_delete() const;
bool can_delete_all() const;
@ -323,9 +356,7 @@ public:
bool can_replace_with_stl() const;
bool can_mirror() const;
bool can_split(bool to_objects) const;
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
bool can_scale_to_print_volume() const;
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
void msw_rescale();
void sys_color_changed();

View file

@ -175,6 +175,8 @@ void PreferencesDialog::build()
m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project")
m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard";
else if (opt_key == "default_action_on_dirty_project")
m_values[opt_key] = boost::any_cast<bool>(value) ? "" : "0";
else
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
@ -259,21 +261,30 @@ void PreferencesDialog::build()
m_optgroup_general->append_separator();
append_bool_option(m_optgroup_general, "default_action_on_dirty_project",
L("Ask for unsaved changes in project"),
L("Always ask for unsaved changes in project, when: \n"
"- Closing PrusaSlicer,\n"
"- Loading or creating a new project"),
app_config->get("default_action_on_dirty_project").empty());
m_optgroup_general->append_separator();
append_bool_option(m_optgroup_general, "default_action_on_close_application",
L("Ask to save unsaved changes when closing the application or when loading a new project"),
L("Always ask for unsaved changes, when: \n"
L("Ask to save unsaved changes in presets when closing the application or when loading a new project"),
L("Always ask for unsaved changes in presets, when: \n"
"- Closing PrusaSlicer while some presets are modified,\n"
"- Loading a new project while some presets are modified"),
app_config->get("default_action_on_close_application") == "none");
append_bool_option(m_optgroup_general, "default_action_on_select_preset",
L("Ask for unsaved changes when selecting new preset"),
L("Always ask for unsaved changes when selecting new preset or resetting a preset"),
L("Ask for unsaved changes in presets when selecting new preset"),
L("Always ask for unsaved changes in presets when selecting new preset or resetting a preset"),
app_config->get("default_action_on_select_preset") == "none");
append_bool_option(m_optgroup_general, "default_action_on_new_project",
L("Ask for unsaved changes when creating new project"),
L("Always ask for unsaved changes when creating new project"),
L("Ask for unsaved changes in presets when creating new project"),
L("Always ask for unsaved changes in presets when creating new project"),
app_config->get("default_action_on_new_project") == "none");
}
#ifdef _WIN32
@ -301,6 +312,11 @@ void PreferencesDialog::build()
L("Show splash screen"),
app_config->get("show_splash_screen") == "1");
append_bool_option(m_optgroup_general, "restore_win_position",
L("Restore window position on start"),
L("If enabled, PrusaSlicer will be open at the position it was closed"),
app_config->get("restore_win_position") == "1");
// Clear Undo / Redo stack on new project
append_bool_option(m_optgroup_general, "clear_undo_redo_stack_on_new_project",
L("Clear Undo / Redo stack on new project"),
@ -377,8 +393,9 @@ void PreferencesDialog::build()
append_bool_option(m_optgroup_gui, "suppress_hyperlinks",
L("Suppress to open hyperlink in browser"),
L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. "
"If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."),
L("If enabled, PrusaSlicer will not open a hyperlinks in your browser."),
//L("If enabled, the descriptions of configuration parameters in settings tabs wouldn't work as hyperlinks. "
// "If disabled, the descriptions of configuration parameters in settings tabs will work as hyperlinks."),
app_config->get("suppress_hyperlinks") == "1");
append_bool_option(m_optgroup_gui, "color_mapinulation_panel",
@ -568,10 +585,17 @@ void PreferencesDialog::accept(wxEvent&)
}
}
for (const std::string& key : {"default_action_on_close_application", "default_action_on_select_preset"}) {
for (const std::string& key : { "default_action_on_close_application",
"default_action_on_select_preset",
"default_action_on_new_project" }) {
auto it = m_values.find(key);
if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none")
m_values.erase(it); // we shouldn't change value, if some of those parameters was selected, and then deselected
m_values.erase(it); // we shouldn't change value, if some of those parameters were selected, and then deselected
}
{
auto it = m_values.find("default_action_on_dirty_project");
if (it != m_values.end() && !it->second.empty() && !app_config->get("default_action_on_dirty_project").empty())
m_values.erase(it); // we shouldn't change value, if this parameter was selected, and then deselected
}
#if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application

View file

@ -247,7 +247,7 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
append_text_column(_L("Status"),widths[2]);
append_text_column(_L("Host"), widths[3]);
append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]);
append_text_column(_CTX(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]);
append_text_column(_L("Filename"), widths[5]);
append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
@ -324,7 +324,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job)
} else
stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB";
fields.push_back(wxVariant(stream.str()));
fields.push_back(wxVariant(job.upload_data.upload_path.string()));
fields.push_back(wxVariant(from_path(job.upload_data.upload_path)));
fields.push_back(wxVariant(""));
job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW));
// Both strings are UTF-8 encoded.

View file

@ -15,9 +15,7 @@
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
#include "libslic3r/BuildVolume.hpp"
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
#include <GL/glew.h>
@ -454,7 +452,6 @@ void Selection::clear()
if (m_list.empty())
return;
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
// ensure that the volumes get the proper color before next call to render (expecially needed for transparent volumes)
for (unsigned int i : m_list) {
GLVolume& volume = *(*m_volumes)[i];
@ -470,13 +467,6 @@ void Selection::clear()
if (is_transparent)
volume.force_transparent = false;
}
#else
for (unsigned int i : m_list) {
(*m_volumes)[i]->selected = false;
// ensure the volume gets the proper color before next call to render (expecially needed for transparent volumes)
(*m_volumes)[i]->set_render_color();
}
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
m_list.clear();
@ -961,7 +951,6 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type
wxGetApp().plater()->canvas3D()->requires_check_outside_state();
}
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
void Selection::scale_to_fit_print_volume(const BuildVolume& volume)
{
auto fit = [this](double s, Vec3d offset) {
@ -1049,50 +1038,6 @@ void Selection::scale_to_fit_print_volume(const BuildVolume& volume)
default: { break; }
}
}
#else
void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config)
{
if (is_empty() || m_mode == Volume)
return;
// adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings
Vec3d box_size = get_bounding_box().size() + 0.01 * Vec3d::Ones();
const ConfigOptionPoints* opt = dynamic_cast<const ConfigOptionPoints*>(config.option("bed_shape"));
if (opt != nullptr) {
BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values));
BoundingBoxf3 print_volume({ unscale<double>(bed_box_2D.min(0)), unscale<double>(bed_box_2D.min(1)), 0.0 }, { unscale<double>(bed_box_2D.max(0)), unscale<double>(bed_box_2D.max(1)), config.opt_float("max_print_height") });
Vec3d print_volume_size = print_volume.size();
double sx = (box_size(0) != 0.0) ? print_volume_size(0) / box_size(0) : 0.0;
double sy = (box_size(1) != 0.0) ? print_volume_size(1) / box_size(1) : 0.0;
double sz = (box_size(2) != 0.0) ? print_volume_size(2) / box_size(2) : 0.0;
if (sx != 0.0 && sy != 0.0 && sz != 0.0)
{
double s = std::min(sx, std::min(sy, sz));
if (s != 1.0) {
wxGetApp().plater()->take_snapshot(_L("Scale To Fit"));
TransformationType type;
type.set_world();
type.set_relative();
type.set_joint();
// apply scale
start_dragging();
scale(s * Vec3d::Ones(), type);
wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot
// center selection on print bed
start_dragging();
translate(print_volume.center() - get_bounding_box().center());
wxGetApp().plater()->canvas3D()->do_move(""); // avoid storing another snapshot
wxGetApp().obj_manipul()->set_dirty();
}
}
}
}
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
void Selection::mirror(Axis axis)
{

View file

@ -17,9 +17,7 @@ class GLArrow;
class GLCurvedArrow;
class DynamicPrintConfig;
class GLShaderProgram;
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
class BuildVolume;
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
using GLVolumePtrs = std::vector<GLVolume*>;
using ModelObjectPtrs = std::vector<ModelObject*>;
@ -323,11 +321,7 @@ public:
void rotate(const Vec3d& rotation, TransformationType transformation_type);
void flattening_rotate(const Vec3d& normal);
void scale(const Vec3d& scale, TransformationType transformation_type);
#if ENABLE_ENHANCED_PRINT_VOLUME_FIT
void scale_to_fit_print_volume(const BuildVolume& volume);
#else
void scale_to_fit_print_volume(const DynamicPrintConfig& config);
#endif // ENABLE_ENHANCED_PRINT_VOLUME_FIT
void mirror(Axis axis);
void translate(unsigned int object_idx, const Vec3d& displacement);

View file

@ -116,7 +116,7 @@ SysInfoDialog::SysInfoDialog()
// main_info_text
wxFont font = get_default_font(this);
const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
const auto text_clr = wxGetApp().get_label_clr_default();
#if ENABLE_COLOR_CLASSES
auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));

View file

@ -2298,10 +2298,13 @@ void TabPrinter::build_fff()
}
}
if (opt_key == "gcode_flavor") {
bool supports_travel_acceleration = (boost::any_cast<int>(value) == int(gcfMarlinFirmware));
if (supports_travel_acceleration != m_supports_travel_acceleration) {
const int flavor = boost::any_cast<int>(value);
bool supports_travel_acceleration = (flavor == int(gcfMarlinFirmware) || flavor == int(gcfRepRapFirmware));
bool supports_min_feedrates = (flavor == int(gcfMarlinFirmware) || flavor == int(gcfMarlinLegacy));
if (supports_travel_acceleration != m_supports_travel_acceleration || supports_min_feedrates != m_supports_min_feedrates) {
m_rebuild_kinematics_page = true;
m_supports_travel_acceleration = supports_travel_acceleration;
m_supports_min_feedrates = supports_min_feedrates;
}
}
build_unregular_pages();
@ -2586,9 +2589,11 @@ PageShp TabPrinter::build_kinematics_page()
append_option_line(optgroup, "machine_max_jerk_" + axis);
}
optgroup = page->new_optgroup(L("Minimum feedrates"));
append_option_line(optgroup, "machine_min_extruding_rate");
append_option_line(optgroup, "machine_min_travel_rate");
if (m_supports_min_feedrates) {
optgroup = page->new_optgroup(L("Minimum feedrates"));
append_option_line(optgroup, "machine_min_extruding_rate");
append_option_line(optgroup, "machine_min_travel_rate");
}
return page;
}
@ -2603,7 +2608,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
{
size_t n_before_extruders = 2; // Count of pages before Extruder pages
auto flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value;
bool is_marlin_flavor = (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware);
bool show_mach_limits = (flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
/* ! Freeze/Thaw in this function is needed to avoid call OnPaint() for erased pages
* and be cause of application crash, when try to change Preset in moment,
@ -2611,26 +2616,26 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
* */
Freeze();
// Add/delete Kinematics page according to is_marlin_flavor
// Add/delete Kinematics page according to show_mach_limits
size_t existed_page = 0;
for (size_t i = n_before_extruders; i < m_pages.size(); ++i) // first make sure it's not there already
if (m_pages[i]->title().find(L("Machine limits")) != std::string::npos) {
if (!is_marlin_flavor || m_rebuild_kinematics_page)
if (!show_mach_limits || m_rebuild_kinematics_page)
m_pages.erase(m_pages.begin() + i);
else
existed_page = i;
break;
}
if (existed_page < n_before_extruders && (is_marlin_flavor || from_initial_build)) {
if (existed_page < n_before_extruders && (show_mach_limits || from_initial_build)) {
auto page = build_kinematics_page();
if (from_initial_build && !is_marlin_flavor)
if (from_initial_build && !show_mach_limits)
page->clear();
else
m_pages.insert(m_pages.begin() + n_before_extruders, page);
}
if (is_marlin_flavor)
if (show_mach_limits)
n_before_extruders++;
size_t n_after_single_extruder_MM = 2; // Count of pages after single_extruder_multi_material page
@ -2868,13 +2873,13 @@ void TabPrinter::toggle_options()
if (!m_active_page || m_presets->get_edited_preset().printer_technology() == ptSLA)
return;
const GCodeFlavor flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value;
bool have_multiple_extruders = m_extruders_count > 1;
if (m_active_page->title() == "Custom G-code")
toggle_option("toolchange_gcode", have_multiple_extruders);
if (m_active_page->title() == "General") {
toggle_option("single_extruder_multi_material", have_multiple_extruders);
auto flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value;
bool is_marlin_flavor = flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware;
// Disable silent mode for non-marlin firmwares.
toggle_option("silent_mode", is_marlin_flavor);
@ -2944,8 +2949,9 @@ void TabPrinter::toggle_options()
}
if (m_active_page->title() == "Machine limits" && m_machine_limits_description_line) {
assert(m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == gcfMarlinLegacy
|| m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == gcfMarlinFirmware);
assert(flavor == gcfMarlinLegacy
|| flavor == gcfMarlinFirmware
|| flavor == gcfRepRapFirmware);
const auto *machine_limits_usage = m_config->option<ConfigOptionEnum<MachineLimitsUsage>>("machine_limits_usage");
bool enabled = machine_limits_usage->value != MachineLimitsUsage::Ignore;
bool silent_mode = m_config->opt_bool("silent_mode");
@ -2977,10 +2983,13 @@ void TabPrinter::update_fff()
m_use_silent_mode = m_config->opt_bool("silent_mode");
}
bool supports_travel_acceleration = (m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value == gcfMarlinFirmware);
if (m_supports_travel_acceleration != supports_travel_acceleration) {
const auto flavor = m_config->option<ConfigOptionEnum<GCodeFlavor>>("gcode_flavor")->value;
bool supports_travel_acceleration = (flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
bool supports_min_feedrates = (flavor == gcfMarlinFirmware || flavor == gcfMarlinLegacy);
if (m_supports_travel_acceleration != supports_travel_acceleration || m_supports_min_feedrates != supports_min_feedrates) {
m_rebuild_kinematics_page = true;
m_supports_travel_acceleration = supports_travel_acceleration;
m_supports_min_feedrates = supports_min_feedrates;
}
toggle_options();

View file

@ -408,6 +408,7 @@ private:
bool m_has_single_extruder_MM_page = false;
bool m_use_silent_mode = false;
bool m_supports_travel_acceleration = false;
bool m_supports_min_feedrates = false;
void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key);
bool m_rebuild_kinematics_page = false;
ogStaticText* m_machine_limits_description_line {nullptr};

View file

@ -60,7 +60,7 @@ static std::string get_icon_name(Preset::Type type, PrinterTechnology pt) {
static std::string def_text_color()
{
wxColour def_colour = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
wxColour def_colour = wxGetApp().get_label_clr_default();
#if ENABLE_COLOR_CLASSES
return encode_color(ColorRGB(def_colour.Red(), def_colour.Green(), def_colour.Blue()));
#else
@ -903,12 +903,12 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_
{
if (!evt.IsChecked())
return;
wxString preferences_item = m_app_config_key == "default_action_on_new_project" ? _L("Ask for unsaved changes when creating new project") :
m_app_config_key == "default_action_on_select_preset" ? _L("Ask for unsaved changes when selecting new preset") :
_L("Ask to save unsaved changes when closing the application or when loading a new project") ;
wxString action = m_app_config_key == "default_action_on_new_project" ? _L("You will not be asked about the unsaved changes the next time you create new project") :
m_app_config_key == "default_action_on_select_preset" ? _L("You will not be asked about the unsaved changes the next time you switch a preset") :
_L("You will not be asked about the unsaved changes the next time you: \n"
wxString preferences_item = m_app_config_key == "default_action_on_new_project" ? _L("Ask for unsaved changes in presets when creating new project") :
m_app_config_key == "default_action_on_select_preset" ? _L("Ask for unsaved changes in presets when selecting new preset") :
_L("Ask to save unsaved changes in presets when closing the application or when loading a new project") ;
wxString action = m_app_config_key == "default_action_on_new_project" ? _L("You will not be asked about the unsaved changes in presets the next time you create new project") :
m_app_config_key == "default_action_on_select_preset" ? _L("You will not be asked about the unsaved changes in presets the next time you switch a preset") :
_L("You will not be asked about the unsaved changes in presets the next time you: \n"
"- Closing PrusaSlicer while some presets are modified,\n"
"- Loading a new project while some presets are modified") ;
wxString msg = _L("PrusaSlicer will remember your action.") + "\n\n" + action + "\n\n" +
@ -939,7 +939,7 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_
void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name)
{
if (action == Action::Undef && !m_has_long_strings)
if (action == Action::Undef && !m_tree->has_long_strings())
m_info_line->Hide();
else {
wxString text;
@ -1453,7 +1453,7 @@ DiffPresetDialog::DiffPresetDialog(MainFrame* mainframe)
m_preset_bundle_left = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
m_preset_bundle_right = std::make_unique<PresetBundle>(*wxGetApp().preset_bundle);
m_top_info_line = new wxStaticText(this, wxID_ANY, "Select presets to compare");
m_top_info_line = new wxStaticText(this, wxID_ANY, _L("Select presets to compare"));
m_top_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold());
m_bottom_info_line = new wxStaticText(this, wxID_ANY, "");

View file

@ -221,6 +221,7 @@ public:
void item_value_changed(wxDataViewEvent& event);
void set_em_unit(int em) { m_em_unit = em; }
bool has_unselected_options();
bool has_long_strings() { return m_has_long_strings; }
std::vector<std::string> options(Preset::Type type, bool selected);
std::vector<std::string> selected_options();
@ -240,7 +241,6 @@ class UnsavedChangesDialog : public DPIDialog
wxStaticText* m_info_line { nullptr };
wxCheckBox* m_remember_choice { nullptr };
bool m_has_long_strings { false };
int m_save_btn_id { wxID_ANY };
int m_move_btn_id { wxID_ANY };
int m_continue_btn_id { wxID_ANY };

View file

@ -284,29 +284,32 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path
// Open the destination file.
FILE *fout = boost::nowide::fopen(path_dst.c_str(), "wb");
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
byte *buffer_ptr;
bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
{
Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
buffer.As(&bufferByteAccess);
hr = bufferByteAccess->Buffer(&buffer_ptr);
}
uint32_t length;
hr = buffer->get_Length(&length);
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
for (;;) {
hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
status = winrt_async_await(asyncRead, throw_on_cancel);
if (status != AsyncStatus::Completed)
throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
try {
Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IBuffer> buffer;
byte *buffer_ptr;
bufferFactory->Create(65536 * 2048, buffer.GetAddressOf());
{
Microsoft::WRL::ComPtr<Windows::Storage::Streams::IBufferByteAccess> bufferByteAccess;
buffer.As(&bufferByteAccess);
hr = bufferByteAccess->Buffer(&buffer_ptr);
}
uint32_t length;
hr = buffer->get_Length(&length);
if (length == 0)
break;
fwrite(buffer_ptr, length, 1, fout);
}
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperationWithProgress<ABI::Windows::Storage::Streams::IBuffer*, UINT32>> asyncRead;
for (;;) {
hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf());
status = winrt_async_await(asyncRead, throw_on_cancel);
if (status != AsyncStatus::Completed)
throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed."));
hr = buffer->get_Length(&length);
if (length == 0)
break;
fwrite(buffer_ptr, length, 1, fout);
}
} catch (...) {
fclose(fout);
throw;
}
fclose(fout);
// Here all the COM objects will be released through the ComPtr destructors.
}