Fixed conflicts after merge with dev branch
This commit is contained in:
commit
a2dc1cd446
147 changed files with 435771 additions and 1275 deletions
|
@ -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");
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ public:
|
|||
bool has_section(const std::string §ion) const
|
||||
{ return m_storage.find(section) != m_storage.end(); }
|
||||
const std::map<std::string, std::string>& get_section(const std::string §ion) 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 §ion, const std::map<std::string, std::string>& data)
|
||||
{ m_storage[section] = data; }
|
||||
void clear_section(const std::string §ion)
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include <libslic3r/SLA/RasterBase.hpp>
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
|
||||
// For rasterizing
|
||||
#include <agg/agg_basics.h>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include <cstdint>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
//#include <libslic3r/SLA/Concurrency.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "MTUtils.hpp"
|
||||
#include "Zipper.hpp"
|
||||
|
||||
#include "libslic3r/Execution/ExecutionTBB.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum SLAPrintStep : unsigned int {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
//====================
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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")));
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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*/)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"))) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define slic3r_GLGizmoRotate_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "../Jobs/RotoptimizeJob.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
|
|
@ -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"))) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 &);
|
||||
|
|
181
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal file
181
src/slic3r/GUI/Jobs/BoostThreadWorker.cpp
Normal 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
|
140
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal file
140
src/slic3r/GUI/Jobs/BoostThreadWorker.hpp
Normal 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
|
48
src/slic3r/GUI/Jobs/BusyCursorJob.hpp
Normal file
48
src/slic3r/GUI/Jobs/BusyCursorJob.hpp
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ class NotificationManager;
|
|||
|
||||
class NotificationProgressIndicator: public ProgressIndicator {
|
||||
NotificationManager *m_nm = nullptr;
|
||||
|
||||
CancelFn m_cancelfn;
|
||||
public:
|
||||
|
||||
explicit NotificationProgressIndicator(NotificationManager *nm);
|
||||
|
|
|
@ -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
|
|
@ -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
|
127
src/slic3r/GUI/Jobs/PlaterWorker.hpp
Normal file
127
src/slic3r/GUI/Jobs/PlaterWorker.hpp
Normal 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
|
|
@ -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();
|
||||
}
|
||||
|
||||
}}
|
||||
|
|
|
@ -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 ¶ms)>;
|
||||
|
@ -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); }
|
||||
|
||||
|
|
114
src/slic3r/GUI/Jobs/SLAImportDialog.hpp
Normal file
114
src/slic3r/GUI/Jobs/SLAImportDialog.hpp
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
123
src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp
Normal file
123
src/slic3r/GUI/Jobs/ThreadSafeQueue.hpp
Normal 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
|
119
src/slic3r/GUI/Jobs/Worker.hpp
Normal file
119
src/slic3r/GUI/Jobs/Worker.hpp
Normal 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
|
|
@ -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"),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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, "");
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue