mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_reload_from_disk_changes
This commit is contained in:
		
						commit
						a74f3e3fc0
					
				
					 66 changed files with 3959 additions and 633 deletions
				
			
		
							
								
								
									
										2449
									
								
								src/fast_float/fast_float.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2449
									
								
								src/fast_float/fast_float.h
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -467,6 +467,8 @@ Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::Polygons | |||
|     { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } | ||||
| Slic3r::Polygons diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) | ||||
|     { return _clipper(ClipperLib::ctDifference, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } | ||||
| Slic3r::Polygons intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) | ||||
|     { return _clipper(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } | ||||
| Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) | ||||
|     { return _clipper(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } | ||||
| Slic3r::Polygons intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset) | ||||
|  | @ -496,6 +498,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfac | |||
|     { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset) | ||||
|     { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); } | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset) | ||||
|     { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::SinglePathProvider(clip.points), do_safety_offset); } | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) | ||||
|     { return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::ExPolygonProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); } | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset) | ||||
|  | @ -610,12 +614,18 @@ Polylines _clipper_pl_closed(ClipperLib::ClipType clipType, PathProvider1 &&subj | |||
| 
 | ||||
| Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip) | ||||
|     { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } | ||||
| Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip) | ||||
|     { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); } | ||||
| Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) | ||||
|     { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } | ||||
| Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) | ||||
|     { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonsProvider(clip)); } | ||||
| Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip) | ||||
|     { return _clipper_pl_closed(ClipperLib::ctDifference, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip)); } | ||||
| Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip) | ||||
|     { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::SinglePathProvider(clip.points)); } | ||||
| Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip) | ||||
|     { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::PolygonsProvider(clip)); } | ||||
| Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip) | ||||
|     { return _clipper_pl_open(ClipperLib::ctIntersection, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } | ||||
| Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) | ||||
|  | @ -637,7 +647,9 @@ Lines _clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Pol | |||
|     // convert Polylines to Lines
 | ||||
|     Lines retval; | ||||
|     for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) | ||||
|         retval.emplace_back(polyline->operator Line()); | ||||
|         if (polyline->size() >= 2) | ||||
|             //FIXME It may happen, that Clipper produced a polyline with more than 2 collinear points by clipping a single line with polygons. It is a very rare issue, but it happens, see GH #6933.
 | ||||
|             retval.push_back({ polyline->front(), polyline->back() }); | ||||
|     return retval; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -303,6 +303,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygo | |||
| Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::Polygon &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygon &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
|  | @ -312,6 +313,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surf | |||
| Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::Polylines  diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); | ||||
| Slic3r::Polylines  diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); | ||||
| Slic3r::Polylines  diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); | ||||
| Slic3r::Polylines  diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); | ||||
| Slic3r::Polylines  diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); | ||||
|  | @ -322,6 +324,7 @@ inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygon | |||
| } | ||||
| 
 | ||||
| // Safety offset is applied to the clipping polygons only.
 | ||||
| Slic3r::Polygons   intersection(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::Polygons   intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::Polygons   intersection(const Slic3r::ExPolygon &subject, const Slic3r::ExPolygon &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::Polygons   intersection(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
|  | @ -337,6 +340,8 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r | |||
| Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); | ||||
| Slic3r::Polylines  intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); | ||||
| Slic3r::Polylines  intersection_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip); | ||||
| Slic3r::Polylines  intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); | ||||
| Slic3r::Polylines  intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); | ||||
| Slic3r::Polylines  intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); | ||||
|  |  | |||
|  | @ -92,7 +92,7 @@ bool ExPolygon::contains(const Line &line) const | |||
| 
 | ||||
| bool ExPolygon::contains(const Polyline &polyline) const | ||||
| { | ||||
|     return diff_pl((Polylines)polyline, *this).empty(); | ||||
|     return diff_pl(polyline, *this).empty(); | ||||
| } | ||||
| 
 | ||||
| bool ExPolygon::contains(const Polylines &polylines) const | ||||
|  |  | |||
|  | @ -928,7 +928,9 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b | |||
|                             Linef l { { bg::get<0, 0>(seg), bg::get<0, 1>(seg) }, { bg::get<1, 0>(seg), bg::get<1, 1>(seg) } }; | ||||
|                             assert(line_alg::distance_to_squared(l, Vec2d(pt.cast<double>())) > 1000 * 1000); | ||||
|     #endif // NDEBUG
 | ||||
|                         } else if (((Line)pl).distance_to_squared(pt) <= 1000 * 1000) | ||||
|                         } else if (pl.size() >= 2 &&  | ||||
|                             //FIXME Hoping that pl is really a line, trimmed by a polygon using ClipperUtils. Sometimes Clipper leaves some additional collinear points on the polyline, let's hope it is all right.
 | ||||
|                             Line{ pl.front(), pl.back() }.distance_to_squared(pt) <= 1000 * 1000) | ||||
|                             out = closest.front().second; | ||||
|                     } | ||||
|                     return out; | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ | |||
| #include "SVG.hpp" | ||||
| 
 | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <tbb/pipeline.h> | ||||
| 
 | ||||
| #include <Shiny/Shiny.h> | ||||
| 
 | ||||
|  | @ -1099,7 +1100,6 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato | |||
|     m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); | ||||
|     print.throw_if_canceled(); | ||||
| 
 | ||||
|     m_cooling_buffer = make_unique<CoolingBuffer>(*this); | ||||
|     if (print.config().spiral_vase.value) | ||||
|         m_spiral_vase = make_unique<SpiralVase>(print.config()); | ||||
| #ifdef HAS_PRESSURE_EQUALIZER | ||||
|  | @ -1212,6 +1212,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato | |||
|     } | ||||
|     print.throw_if_canceled(); | ||||
| 
 | ||||
|     m_cooling_buffer = make_unique<CoolingBuffer>(*this); | ||||
|     m_cooling_buffer->set_current_extruder(initial_extruder_id); | ||||
| 
 | ||||
|     // Emit machine envelope limits for the Marlin firmware.
 | ||||
|  | @ -1219,7 +1220,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato | |||
| 
 | ||||
|     // Disable fan.
 | ||||
|     if (! print.config().cooling.get_at(initial_extruder_id) || print.config().disable_fan_first_layers.get_at(initial_extruder_id)) | ||||
|         file.write(m_writer.set_fan(0, true)); | ||||
|         file.write(m_writer.set_fan(0)); | ||||
| 
 | ||||
|     // Let the start-up script prime the 1st printing tool.
 | ||||
|     m_placeholder_parser.set("initial_tool", initial_extruder_id); | ||||
|  | @ -1334,17 +1335,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato | |||
|                 file.writeln(between_objects_gcode); | ||||
|             } | ||||
|             // Reset the cooling buffer internal state (the current position, feed rate, accelerations).
 | ||||
|             m_cooling_buffer->reset(); | ||||
|             m_cooling_buffer->reset(this->writer().get_position()); | ||||
|             m_cooling_buffer->set_current_extruder(initial_extruder_id); | ||||
|             // Pair the object layers with the support layers by z, extrude them.
 | ||||
|             std::vector<LayerToPrint> layers_to_print = collect_layers_to_print(object); | ||||
|             for (const LayerToPrint <p : layers_to_print) { | ||||
|                 std::vector<LayerToPrint> lrs; | ||||
|                 lrs.emplace_back(std::move(ltp)); | ||||
|                 this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), <p == &layers_to_print.back(),  | ||||
|                     nullptr, *print_object_instance_sequential_active - object.instances().data()); | ||||
|                 print.throw_if_canceled(); | ||||
|             } | ||||
|             // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
 | ||||
|             // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
 | ||||
|             // and export G-code into file.
 | ||||
|             this->process_layers(print, tool_ordering, collect_layers_to_print(object), *print_object_instance_sequential_active - object.instances().data(), file); | ||||
| #ifdef HAS_PRESSURE_EQUALIZER | ||||
|             if (m_pressure_equalizer) | ||||
|                 file.write(m_pressure_equalizer->process("", true)); | ||||
|  | @ -1401,14 +1397,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato | |||
|             } | ||||
|             print.throw_if_canceled(); | ||||
|         } | ||||
|         // Extrude the layers.
 | ||||
|         for (auto &layer : layers_to_print) { | ||||
|             const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first); | ||||
|             if (m_wipe_tower && layer_tools.has_wipe_tower) | ||||
|                 m_wipe_tower->next_layer(); | ||||
|             this->process_layer(file, print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); | ||||
|             print.throw_if_canceled(); | ||||
|         } | ||||
|         // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
 | ||||
|         // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
 | ||||
|         // and export G-code into file.
 | ||||
|         this->process_layers(print, tool_ordering, print_object_instances_ordering, layers_to_print, file); | ||||
| #ifdef HAS_PRESSURE_EQUALIZER | ||||
|         if (m_pressure_equalizer) | ||||
|             file.write(m_pressure_equalizer->process("", true)); | ||||
|  | @ -1420,7 +1412,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato | |||
| 
 | ||||
|     // Write end commands to file.
 | ||||
|     file.write(this->retract()); | ||||
|     file.write(m_writer.set_fan(false)); | ||||
|     file.write(m_writer.set_fan(0)); | ||||
| 
 | ||||
|     // adds tag for processor
 | ||||
|     file.write_format(";%s%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Role).c_str(), ExtrusionEntity::role_to_string(erCustom).c_str()); | ||||
|  | @ -1481,6 +1473,88 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato | |||
|     print.throw_if_canceled(); | ||||
| } | ||||
| 
 | ||||
| // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
 | ||||
| // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
 | ||||
| // and export G-code into file.
 | ||||
| void GCode::process_layers( | ||||
|     const Print                                                         &print, | ||||
|     const ToolOrdering                                                  &tool_ordering, | ||||
|     const std::vector<const PrintInstance*>                             &print_object_instances_ordering, | ||||
|     const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>>   &layers_to_print, | ||||
|     GCodeOutputStream                                                   &output_stream) | ||||
| { | ||||
|     // The pipeline is variable: The vase mode filter is optional.
 | ||||
|     size_t layer_to_print_idx = 0; | ||||
|     const auto generator = tbb::make_filter<void, GCode::LayerResult>(tbb::filter::serial_in_order, | ||||
|         [this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult { | ||||
|             if (layer_to_print_idx == layers_to_print.size()) { | ||||
|                 fc.stop(); | ||||
|                 return {}; | ||||
|             } else { | ||||
|                 const std::pair<coordf_t, std::vector<LayerToPrint>>& layer = layers_to_print[layer_to_print_idx++]; | ||||
|                 const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first); | ||||
|                 if (m_wipe_tower && layer_tools.has_wipe_tower) | ||||
|                     m_wipe_tower->next_layer(); | ||||
|                 print.throw_if_canceled(); | ||||
|                 return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1)); | ||||
|             } | ||||
|         }); | ||||
|     const auto spiral_vase = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(tbb::filter::serial_in_order, | ||||
|         [&spiral_vase = *this->m_spiral_vase.get()](GCode::LayerResult in) -> GCode::LayerResult { | ||||
|             spiral_vase.enable(in.spiral_vase_enable); | ||||
|             return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; | ||||
|         }); | ||||
|     const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(tbb::filter::serial_in_order, | ||||
|         [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { | ||||
|             return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); | ||||
|         }); | ||||
|     const auto output = tbb::make_filter<std::string, void>(tbb::filter::serial_in_order, | ||||
|         [&output_stream](std::string s) { output_stream.write(s); } | ||||
|     ); | ||||
| 
 | ||||
|     // The pipeline elements are joined using const references, thus no copying is performed.
 | ||||
|     if (m_spiral_vase) | ||||
|         tbb::parallel_pipeline(12, generator & spiral_vase & cooling & output); | ||||
|     else | ||||
|         tbb::parallel_pipeline(12, generator & cooling & output); | ||||
| } | ||||
| 
 | ||||
| // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
 | ||||
| // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
 | ||||
| // and export G-code into file.
 | ||||
| void GCode::process_layers( | ||||
|     const Print                             &print, | ||||
|     const ToolOrdering                      &tool_ordering, | ||||
|     std::vector<LayerToPrint>                layers_to_print, | ||||
|     const size_t                             single_object_idx, | ||||
|     GCodeOutputStream                       &output_stream) | ||||
| { | ||||
|     // The pipeline is fixed: Neither wipe tower nor vase mode are implemented for sequential print.
 | ||||
|     size_t layer_to_print_idx = 0; | ||||
|     tbb::parallel_pipeline(12, | ||||
|         tbb::make_filter<void, GCode::LayerResult>( | ||||
|             tbb::filter::serial_in_order, | ||||
|             [this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> GCode::LayerResult { | ||||
|                 if (layer_to_print_idx == layers_to_print.size()) { | ||||
|                     fc.stop(); | ||||
|                     return {}; | ||||
|                 } else { | ||||
|                     LayerToPrint &layer = layers_to_print[layer_to_print_idx ++]; | ||||
|                     print.throw_if_canceled(); | ||||
|                     return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx); | ||||
|                 } | ||||
|             }) & | ||||
|         tbb::make_filter<GCode::LayerResult, std::string>( | ||||
|             tbb::filter::serial_in_order, | ||||
|             [&cooling_buffer = *this->m_cooling_buffer.get()](GCode::LayerResult in) -> std::string { | ||||
|                 return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush); | ||||
|             }) & | ||||
|         tbb::make_filter<std::string, void>( | ||||
|             tbb::filter::serial_in_order, | ||||
|             [&output_stream](std::string s) { output_stream.write(s); } | ||||
|         )); | ||||
| } | ||||
| 
 | ||||
| std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) | ||||
| { | ||||
|     try { | ||||
|  | @ -1890,9 +1964,7 @@ namespace Skirt { | |||
| // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
 | ||||
| // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
 | ||||
| // and performing the extruder specific extrusions together.
 | ||||
| void GCode::process_layer( | ||||
|     // Write into the output file.
 | ||||
|     GCodeOutputStream                       &file, | ||||
| GCode::LayerResult GCode::process_layer( | ||||
|     const Print                    			&print, | ||||
|     // Set of object & print layers of the same PrintObject and with the same print_z.
 | ||||
|     const std::vector<LayerToPrint> 		&layers, | ||||
|  | @ -1908,11 +1980,6 @@ void GCode::process_layer( | |||
|     // Either printing all copies of all objects, or just a single copy of a single object.
 | ||||
|     assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); | ||||
| 
 | ||||
|     if (layer_tools.extruders.empty()) | ||||
|         // Nothing to extrude.
 | ||||
|         return; | ||||
| 
 | ||||
|     // Extract 1st object_layer and support_layer of this set of layers with an equal print_z.
 | ||||
|     const Layer         *object_layer  = nullptr; | ||||
|     const SupportLayer  *support_layer = nullptr; | ||||
|     for (const LayerToPrint &l : layers) { | ||||
|  | @ -1922,6 +1989,12 @@ void GCode::process_layer( | |||
|             support_layer = l.support_layer; | ||||
|     } | ||||
|     const Layer         &layer         = (object_layer != nullptr) ? *object_layer : *support_layer; | ||||
|     GCode::LayerResult   result { {}, layer.id(), false, last_layer }; | ||||
|     if (layer_tools.extruders.empty()) | ||||
|         // Nothing to extrude.
 | ||||
|         return result; | ||||
| 
 | ||||
|     // Extract 1st object_layer and support_layer of this set of layers with an equal print_z.
 | ||||
|     coordf_t             print_z       = layer.print_z; | ||||
|     bool                 first_layer   = layer.id() == 0; | ||||
|     unsigned int         first_extruder_id = layer_tools.extruders.front(); | ||||
|  | @ -1943,7 +2016,7 @@ void GCode::process_layer( | |||
|                     break; | ||||
|                 } | ||||
|         } | ||||
|         m_spiral_vase->enable(enable); | ||||
|         result.spiral_vase_enable = enable; | ||||
|         // If we're going to apply spiralvase to this layer, disable loop clipping.
 | ||||
|         m_enable_loop_clipping = !enable; | ||||
|     } | ||||
|  | @ -2285,6 +2358,7 @@ void GCode::process_layer( | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| #if 0 | ||||
|     // Apply spiral vase post-processing if this layer contains suitable geometry
 | ||||
|     // (we must feed all the G-code into the post-processor, including the first
 | ||||
|     // bottom non-spiral layers otherwise it will mess with positions)
 | ||||
|  | @ -2308,8 +2382,14 @@ void GCode::process_layer( | |||
| #endif /* HAS_PRESSURE_EQUALIZER */ | ||||
| 
 | ||||
|     file.write(gcode); | ||||
| #endif | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << | ||||
|     log_memory_info(); | ||||
| 
 | ||||
|     result.gcode = std::move(gcode); | ||||
|     result.cooling_buffer_flush = object_layer || last_layer; | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| void GCode::apply_print_config(const PrintConfig &print_config) | ||||
|  |  | |||
|  | @ -215,9 +215,16 @@ private: | |||
| 
 | ||||
|     static std::vector<LayerToPrint>        		                   collect_layers_to_print(const PrintObject &object); | ||||
|     static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print); | ||||
|     void            process_layer( | ||||
|         // Write into the output file.
 | ||||
|         GCodeOutputStream               &file, | ||||
| 
 | ||||
|     struct LayerResult { | ||||
|         std::string gcode; | ||||
|         size_t      layer_id; | ||||
|         // Is spiral vase post processing enabled for this layer?
 | ||||
|         bool        spiral_vase_enable { false }; | ||||
|         // Should the cooling buffer content be flushed at the end of this layer?
 | ||||
|         bool        cooling_buffer_flush { false }; | ||||
|     }; | ||||
|     LayerResult process_layer( | ||||
|         const Print                     &print, | ||||
|         // Set of object & print layers of the same PrintObject and with the same print_z.
 | ||||
|         const std::vector<LayerToPrint> &layers, | ||||
|  | @ -228,6 +235,24 @@ private: | |||
|         // If set to size_t(-1), then print all copies of all objects.
 | ||||
|         // Otherwise print a single copy of a single object.
 | ||||
|         const size_t                     single_object_idx = size_t(-1)); | ||||
|     // Process all layers of all objects (non-sequential mode) with a parallel pipeline:
 | ||||
|     // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
 | ||||
|     // and export G-code into file.
 | ||||
|     void process_layers( | ||||
|         const Print                                                         &print, | ||||
|         const ToolOrdering                                                  &tool_ordering, | ||||
|         const std::vector<const PrintInstance*>                             &print_object_instances_ordering, | ||||
|         const std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>>   &layers_to_print, | ||||
|         GCodeOutputStream                                                   &output_stream); | ||||
|     // Process all layers of a single object instance (sequential mode) with a parallel pipeline:
 | ||||
|     // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser
 | ||||
|     // and export G-code into file.
 | ||||
|     void process_layers( | ||||
|         const Print                             &print, | ||||
|         const ToolOrdering                      &tool_ordering, | ||||
|         std::vector<LayerToPrint>                layers_to_print, | ||||
|         const size_t                             single_object_idx, | ||||
|         GCodeOutputStream                       &output_stream); | ||||
| 
 | ||||
|     void            set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } | ||||
|     bool            last_pos_defined() const { return m_last_pos_defined; } | ||||
|  |  | |||
|  | @ -16,19 +16,25 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_extruder(0) | ||||
| CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) | ||||
| { | ||||
|     this->reset(); | ||||
|     this->reset(gcodegen.writer().get_position()); | ||||
| 
 | ||||
|     const std::vector<Extruder> &extruders = gcodegen.writer().extruders(); | ||||
|     m_extruder_ids.reserve(extruders.size()); | ||||
|     for (const Extruder &ex : extruders) { | ||||
|         m_num_extruders = std::max(ex.id() + 1, m_num_extruders); | ||||
|         m_extruder_ids.emplace_back(ex.id()); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void CoolingBuffer::reset() | ||||
| void CoolingBuffer::reset(const Vec3d &position) | ||||
| { | ||||
|     m_current_pos.assign(5, 0.f); | ||||
|     Vec3d pos = m_gcodegen.writer().get_position(); | ||||
|     m_current_pos[0] = float(pos(0)); | ||||
|     m_current_pos[1] = float(pos(1)); | ||||
|     m_current_pos[2] = float(pos(2)); | ||||
|     m_current_pos[4] = float(m_gcodegen.config().travel_speed.value); | ||||
|     m_current_pos[0] = float(position.x()); | ||||
|     m_current_pos[1] = float(position.y()); | ||||
|     m_current_pos[2] = float(position.z()); | ||||
|     m_current_pos[4] = float(m_config.travel_speed.value); | ||||
| } | ||||
| 
 | ||||
| struct CoolingLine | ||||
|  | @ -303,30 +309,23 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b | |||
| // Return the list of parsed lines, bucketed by an extruder.
 | ||||
| std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const | ||||
| { | ||||
|     const FullPrintConfig       &config        = m_gcodegen.config(); | ||||
|     const std::vector<Extruder> &extruders     = m_gcodegen.writer().extruders(); | ||||
|     unsigned int                 num_extruders = 0; | ||||
|     for (const Extruder &ex : extruders) | ||||
|         num_extruders = std::max(ex.id() + 1, num_extruders); | ||||
|      | ||||
|     std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size()); | ||||
|     std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(num_extruders, 0); | ||||
|     for (size_t i = 0; i < extruders.size(); ++ i) { | ||||
|     std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size()); | ||||
|     std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); | ||||
|     for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { | ||||
|         PerExtruderAdjustments &adj         = per_extruder_adjustments[i]; | ||||
|         unsigned int            extruder_id = extruders[i].id(); | ||||
|         unsigned int            extruder_id = m_extruder_ids[i]; | ||||
|         adj.extruder_id               = extruder_id; | ||||
|         adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); | ||||
|         adj.slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(extruder_id)); | ||||
|         adj.min_print_speed           = float(config.min_print_speed.get_at(extruder_id)); | ||||
|         adj.cooling_slow_down_enabled = m_config.cooling.get_at(extruder_id); | ||||
|         adj.slowdown_below_layer_time = float(m_config.slowdown_below_layer_time.get_at(extruder_id)); | ||||
|         adj.min_print_speed           = float(m_config.min_print_speed.get_at(extruder_id)); | ||||
|         map_extruder_to_per_extruder_adjustment[extruder_id] = i; | ||||
|     } | ||||
| 
 | ||||
|     const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); | ||||
|     unsigned int      current_extruder  = m_current_extruder; | ||||
|     PerExtruderAdjustments *adjustment  = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; | ||||
|     const char       *line_start = gcode.c_str(); | ||||
|     const char       *line_end   = line_start; | ||||
|     const char        extrusion_axis = get_extrusion_axis(config)[0]; | ||||
|     const char        extrusion_axis = get_extrusion_axis(m_config)[0]; | ||||
|     // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
 | ||||
|     // for a sequence of extrusion moves.
 | ||||
|     size_t            active_speed_modifier = size_t(-1); | ||||
|  | @ -387,7 +386,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std:: | |||
|             } | ||||
|             if ((line.type & CoolingLine::TYPE_G92) == 0) { | ||||
|                 // G0 or G1. Calculate the duration.
 | ||||
|                 if (config.use_relative_e_distances.value) | ||||
|                 if (m_config.use_relative_e_distances.value) | ||||
|                     // Reset extruder accumulator.
 | ||||
|                     current_pos[3] = 0.f; | ||||
|                 float dif[4]; | ||||
|  | @ -430,8 +429,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std:: | |||
|         } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { | ||||
|             line.type = CoolingLine::TYPE_EXTRUDE_END; | ||||
|             active_speed_modifier = size_t(-1); | ||||
|         } else if (boost::starts_with(sline, toolchange_prefix)) { | ||||
|             unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size()); | ||||
|         } else if (boost::starts_with(sline, m_toolchange_prefix)) { | ||||
|             unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); | ||||
|             // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored.
 | ||||
|             if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { | ||||
|                 if (new_extruder != current_extruder) { | ||||
|  | @ -641,7 +640,7 @@ float CoolingBuffer::calculate_layer_slowdown(std::vector<PerExtruderAdjustments | |||
|         if (total > slowdown_below_layer_time) { | ||||
|             // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
 | ||||
|         } else { | ||||
|             // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders.
 | ||||
|             // Adjust this and all the following (higher m_config.slowdown_below_layer_time) extruders.
 | ||||
|             // Sum maximum slow down time as if everything was slowed down including the external perimeters.
 | ||||
|             float max_time = elapsed_time_total0; | ||||
|             for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it) | ||||
|  | @ -694,8 +693,7 @@ std::string CoolingBuffer::apply_layer_cooldown( | |||
|     bool bridge_fan_control = false; | ||||
|     int  bridge_fan_speed   = 0; | ||||
|     auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() { | ||||
|         const FullPrintConfig &config = m_gcodegen.config(); | ||||
| #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder) | ||||
| #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) | ||||
|         int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); | ||||
|         int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; | ||||
|         int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); | ||||
|  | @ -737,13 +735,12 @@ std::string CoolingBuffer::apply_layer_cooldown( | |||
|         } | ||||
|         if (fan_speed_new != fan_speed) { | ||||
|             fan_speed = fan_speed_new; | ||||
|             new_gcode += m_gcodegen.writer().set_fan(fan_speed); | ||||
|             new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     const char         *pos               = gcode.c_str(); | ||||
|     int                 current_feedrate  = 0; | ||||
|     const std::string   toolchange_prefix = m_gcodegen.writer().toolchange_prefix(); | ||||
|     change_extruder_set_fan(); | ||||
|     for (const CoolingLine *line : lines) { | ||||
|         const char *line_start  = gcode.c_str() + line->line_start; | ||||
|  | @ -751,7 +748,7 @@ std::string CoolingBuffer::apply_layer_cooldown( | |||
|         if (line_start > pos) | ||||
|             new_gcode.append(pos, line_start - pos); | ||||
|         if (line->type & CoolingLine::TYPE_SET_TOOL) { | ||||
|             unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size()); | ||||
|             unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); | ||||
|             if (new_extruder != m_current_extruder) { | ||||
|                 m_current_extruder = new_extruder; | ||||
|                 change_extruder_set_fan(); | ||||
|  | @ -759,10 +756,10 @@ std::string CoolingBuffer::apply_layer_cooldown( | |||
|             new_gcode.append(line_start, line_end - line_start); | ||||
|         } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { | ||||
|             if (bridge_fan_control) | ||||
|                 new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true); | ||||
|                 new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); | ||||
|         } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) { | ||||
|             if (bridge_fan_control) | ||||
|                 new_gcode += m_gcodegen.writer().set_fan(fan_speed, true); | ||||
|                 new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, fan_speed); | ||||
|         } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { | ||||
|             // Just remove this comment.
 | ||||
|         } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { | ||||
|  |  | |||
|  | @ -23,10 +23,9 @@ struct PerExtruderAdjustments; | |||
| class CoolingBuffer { | ||||
| public: | ||||
|     CoolingBuffer(GCode &gcodegen); | ||||
|     void        reset(); | ||||
|     void        reset(const Vec3d &position); | ||||
|     void        set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; } | ||||
|     std::string process_layer(std::string &&gcode, size_t layer_id, bool flush); | ||||
|     GCode* 	    gcodegen() { return &m_gcodegen; } | ||||
| 
 | ||||
| private: | ||||
| 	CoolingBuffer& operator=(const CoolingBuffer&) = delete; | ||||
|  | @ -36,17 +35,25 @@ private: | |||
|     // Returns the adjusted G-code.
 | ||||
|     std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments); | ||||
| 
 | ||||
|     GCode&              m_gcodegen; | ||||
|     // G-code snippet cached for the support layers preceding an object layer.
 | ||||
|     std::string         m_gcode; | ||||
|     std::string                 m_gcode; | ||||
|     // Internal data.
 | ||||
|     // X,Y,Z,E,F
 | ||||
|     std::vector<char>   m_axis; | ||||
|     std::vector<float>  m_current_pos; | ||||
|     unsigned int        m_current_extruder; | ||||
|     std::vector<char>           m_axis; | ||||
|     std::vector<float>          m_current_pos; | ||||
|     // Cached from GCodeWriter.
 | ||||
|     // Printing extruder IDs, zero based.
 | ||||
|     std::vector<unsigned int>   m_extruder_ids; | ||||
|     // Highest of m_extruder_ids plus 1.
 | ||||
|     unsigned int                m_num_extruders { 0 }; | ||||
|     const std::string           m_toolchange_prefix; | ||||
|     // Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
 | ||||
|     // the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
 | ||||
|     const PrintConfig          &m_config; | ||||
|     unsigned int                m_current_extruder; | ||||
| 
 | ||||
|     // Old logic: proportional.
 | ||||
|     bool                m_cooling_logic_proportional = false; | ||||
|     bool                        m_cooling_logic_proportional = false; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -54,7 +54,7 @@ std::string SpiralVase::process_layer(const std::string &gcode) | |||
|     // For absolute extruder distances it will be switched off.
 | ||||
|     // Tapering the absolute extruder distances requires to process every extrusion value after the first transition
 | ||||
|     // layer.
 | ||||
|     bool  transition = m_transition_layer && m_config->use_relative_e_distances.value; | ||||
|     bool  transition = m_transition_layer && m_config.use_relative_e_distances.value; | ||||
|     float layer_height_factor = layer_height / total_layer_length; | ||||
|     float len = 0.f; | ||||
|     m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len] | ||||
|  |  | |||
|  | @ -8,10 +8,10 @@ namespace Slic3r { | |||
| 
 | ||||
| class SpiralVase { | ||||
| public: | ||||
|     SpiralVase(const PrintConfig &config) : m_config(&config) | ||||
|     SpiralVase(const PrintConfig &config) : m_config(config) | ||||
|     { | ||||
|         m_reader.z() = (float)m_config->z_offset; | ||||
|         m_reader.apply_config(*m_config); | ||||
|         m_reader.z() = (float)m_config.z_offset; | ||||
|         m_reader.apply_config(m_config); | ||||
|     }; | ||||
| 
 | ||||
|     void 		enable(bool en) { | ||||
|  | @ -22,7 +22,7 @@ public: | |||
|     std::string process_layer(const std::string &gcode); | ||||
|      | ||||
| private: | ||||
|     const PrintConfig  *m_config; | ||||
|     const PrintConfig  &m_config; | ||||
|     GCodeReader 		m_reader; | ||||
| 
 | ||||
|     bool 				m_enabled = false; | ||||
|  | @ -32,4 +32,4 @@ private: | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| #endif // slic3r_SpiralVase_hpp_
 | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ | |||
| #include <boost/algorithm/string/classification.hpp> | ||||
| #include <boost/algorithm/string/split.hpp> | ||||
| #include <boost/nowide/fstream.hpp> | ||||
| #include <charconv> | ||||
| #include <fstream> | ||||
| #include <iostream> | ||||
| #include <iomanip> | ||||
|  | @ -10,6 +9,7 @@ | |||
| #include "LocalesUtils.hpp" | ||||
| 
 | ||||
| #include <Shiny/Shiny.h> | ||||
| #include <fast_float/fast_float.h> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -71,16 +71,9 @@ const char* GCodeReader::parse_line_internal(const char *ptr, const char *end, G | |||
|             } | ||||
|             if (axis != NUM_AXES_WITH_UNKNOWN) { | ||||
|                 // Try to parse the numeric value.
 | ||||
| #ifdef WIN32 | ||||
|                 double v; | ||||
|                 auto [pend, ec] = std::from_chars(++ c, end, v); | ||||
|                 auto [pend, ec] = fast_float::from_chars(++ c, end, v); | ||||
|                 if (pend != c && is_end_of_word(*pend)) { | ||||
| #else | ||||
|                 // The older version of GCC and Clang support std::from_chars just for integers, so strtod we used it instead.
 | ||||
|                 char   *pend = nullptr; | ||||
|                 double  v = strtod(++ c, &pend); | ||||
|                 if (pend != nullptr && is_end_of_word(*pend)) { | ||||
| #endif | ||||
|                     // The axis value has been parsed correctly.
 | ||||
|                     if (axis != UNKNOWN_AXIS) | ||||
| 	                    gline.m_axis[int(axis)] = float(v); | ||||
|  |  | |||
|  | @ -1,21 +1,17 @@ | |||
| #include "GCodeWriter.hpp" | ||||
| #include "CustomGCode.hpp" | ||||
| #include <algorithm> | ||||
| #include <charconv> | ||||
| #include <iomanip> | ||||
| #include <iostream> | ||||
| #include <map> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #define XYZF_EXPORT_DIGITS 3 | ||||
| #define E_EXPORT_DIGITS 5 | ||||
| #ifdef __APPLE__ | ||||
|     #include <boost/spirit/include/karma.hpp> | ||||
| #endif | ||||
| 
 | ||||
| #define FLAVOR_IS(val) this->config.gcode_flavor == val | ||||
| #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val | ||||
| #define COMMENT(comment) if (this->config.gcode_comments && !comment.empty()) gcode << " ; " << comment; | ||||
| #define PRECISION(val, precision) std::fixed << std::setprecision(precision) << (val) | ||||
| #define XYZF_NUM(val) PRECISION(val, XYZF_EXPORT_DIGITS) | ||||
| #define E_NUM(val) PRECISION(val, E_EXPORT_DIGITS) | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
|  | @ -156,41 +152,6 @@ std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait | |||
|     return gcode.str(); | ||||
| } | ||||
| 
 | ||||
| std::string GCodeWriter::set_fan(unsigned int speed, bool dont_save) | ||||
| { | ||||
|     std::ostringstream gcode; | ||||
|     if (m_last_fan_speed != speed || dont_save) { | ||||
|         if (!dont_save) m_last_fan_speed = speed; | ||||
|          | ||||
|         if (speed == 0) { | ||||
|             if (FLAVOR_IS(gcfTeacup)) { | ||||
|                 gcode << "M106 S0"; | ||||
|             } else if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { | ||||
|                 gcode << "M127"; | ||||
|             } else { | ||||
|                 gcode << "M107"; | ||||
|             } | ||||
|             if (this->config.gcode_comments) gcode << " ; disable fan"; | ||||
|             gcode << "\n"; | ||||
|         } else { | ||||
|             if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) { | ||||
|                 gcode << "M126"; | ||||
|             } else { | ||||
|                 gcode << "M106 "; | ||||
|                 if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) { | ||||
|                     gcode << "P"; | ||||
|                 } else { | ||||
|                     gcode << "S"; | ||||
|                 } | ||||
|                 gcode << (255.0 * speed / 100.0); | ||||
|             } | ||||
|             if (this->config.gcode_comments) gcode << " ; enable fan"; | ||||
|             gcode << "\n"; | ||||
|         } | ||||
|     } | ||||
|     return gcode.str(); | ||||
| } | ||||
| 
 | ||||
| std::string GCodeWriter::set_acceleration(unsigned int acceleration) | ||||
| { | ||||
|     // Clamp the acceleration to the allowed maximum.
 | ||||
|  | @ -292,85 +253,12 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id) | |||
|     return gcode.str(); | ||||
| } | ||||
| 
 | ||||
| class G1Writer { | ||||
| private: | ||||
|     static constexpr const size_t   buflen = 256; | ||||
|     char                            buf[buflen]; | ||||
|     char                           *buf_end; | ||||
|     std::to_chars_result            ptr_err; | ||||
| 
 | ||||
| public: | ||||
|     G1Writer() { | ||||
|         this->buf[0] = 'G'; | ||||
|         this->buf[1] = '1'; | ||||
|         this->buf_end = this->buf + buflen; | ||||
|         this->ptr_err.ptr = this->buf + 2; | ||||
|     } | ||||
| 
 | ||||
|     void emit_axis(const char axis, const double v, size_t digits) { | ||||
|         *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = axis; | ||||
| #ifdef WIN32 | ||||
|         this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end, v, std::chars_format::fixed, digits); | ||||
| #else | ||||
|         int buf_capacity = int(this->buf_end - this->ptr_err.ptr); | ||||
|         int ret          = snprintf(this->ptr_err.ptr, buf_capacity, "%.*lf", int(digits), v); | ||||
|         if (ret <= 0 || ret > buf_capacity) | ||||
|             ptr_err.ec = std::errc::value_too_large; | ||||
|         else | ||||
|             this->ptr_err.ptr = this->ptr_err.ptr + ret; | ||||
| #endif | ||||
|     } | ||||
| 
 | ||||
|     void emit_xy(const Vec2d &point) { | ||||
|         this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); | ||||
|         this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); | ||||
|     } | ||||
| 
 | ||||
|     void emit_xyz(const Vec3d &point) { | ||||
|         this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); | ||||
|         this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); | ||||
|         this->emit_z(point.z()); | ||||
|     } | ||||
| 
 | ||||
|     void emit_z(const double z) { | ||||
|         this->emit_axis('Z', z, XYZF_EXPORT_DIGITS); | ||||
|     } | ||||
| 
 | ||||
|     void emit_e(const std::string &axis, double v) { | ||||
|         if (! axis.empty()) { | ||||
|             // not gcfNoExtrusion
 | ||||
|             this->emit_axis(axis[0], v, E_EXPORT_DIGITS); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void emit_f(double speed) { | ||||
|         this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); | ||||
|     } | ||||
| 
 | ||||
|     void emit_string(const std::string &s) { | ||||
|         strncpy(ptr_err.ptr, s.c_str(), s.size()); | ||||
|         ptr_err.ptr += s.size(); | ||||
|     } | ||||
| 
 | ||||
|     void emit_comment(bool allow_comments, const std::string &comment) { | ||||
|         if (allow_comments && ! comment.empty()) { | ||||
|             *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; | ||||
|             this->emit_string(comment); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::string string() { | ||||
|         *ptr_err.ptr ++ = '\n'; | ||||
|         return std::string(this->buf, ptr_err.ptr - buf); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| std::string GCodeWriter::set_speed(double F, const std::string &comment, const std::string &cooling_marker) const | ||||
| { | ||||
|     assert(F > 0.); | ||||
|     assert(F < 100000.); | ||||
| 
 | ||||
|     G1Writer w; | ||||
|     GCodeG1Formatter w; | ||||
|     w.emit_f(F); | ||||
|     w.emit_comment(this->config.gcode_comments, comment); | ||||
|     w.emit_string(cooling_marker); | ||||
|  | @ -382,7 +270,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &com | |||
|     m_pos(0) = point(0); | ||||
|     m_pos(1) = point(1); | ||||
|      | ||||
|     G1Writer w; | ||||
|     GCodeG1Formatter w; | ||||
|     w.emit_xy(point); | ||||
|     w.emit_f(this->config.travel_speed.value * 60.0); | ||||
|     w.emit_comment(this->config.gcode_comments, comment); | ||||
|  | @ -415,7 +303,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co | |||
|     m_lifted = 0; | ||||
|     m_pos = point; | ||||
|      | ||||
|     G1Writer w; | ||||
|     GCodeG1Formatter w; | ||||
|     w.emit_xyz(point); | ||||
|     w.emit_f(this->config.travel_speed.value * 60.0); | ||||
|     w.emit_comment(this->config.gcode_comments, comment); | ||||
|  | @ -449,7 +337,7 @@ std::string GCodeWriter::_travel_to_z(double z, const std::string &comment) | |||
|     if (speed == 0.) | ||||
|         speed = this->config.travel_speed.value; | ||||
|      | ||||
|     G1Writer w; | ||||
|     GCodeG1Formatter w; | ||||
|     w.emit_z(z); | ||||
|     w.emit_f(speed * 60.0); | ||||
|     w.emit_comment(this->config.gcode_comments, comment); | ||||
|  | @ -474,7 +362,7 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std: | |||
|     m_pos(1) = point(1); | ||||
|     m_extruder->extrude(dE); | ||||
| 
 | ||||
|     G1Writer w; | ||||
|     GCodeG1Formatter w; | ||||
|     w.emit_xy(point); | ||||
|     w.emit_e(m_extrusion_axis, m_extruder->E()); | ||||
|     w.emit_comment(this->config.gcode_comments, comment); | ||||
|  | @ -487,7 +375,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std | |||
|     m_lifted = 0; | ||||
|     m_extruder->extrude(dE); | ||||
|      | ||||
|     G1Writer w; | ||||
|     GCodeG1Formatter w; | ||||
|     w.emit_xyz(point); | ||||
|     w.emit_e(m_extrusion_axis, m_extruder->E()); | ||||
|     w.emit_comment(this->config.gcode_comments, comment); | ||||
|  | @ -518,12 +406,11 @@ std::string GCodeWriter::retract_for_toolchange(bool before_wipe) | |||
| 
 | ||||
| std::string GCodeWriter::_retract(double length, double restart_extra, const std::string &comment) | ||||
| { | ||||
|     std::ostringstream gcode; | ||||
|      | ||||
|     /*  If firmware retraction is enabled, we use a fake value of 1
 | ||||
|         since we ignore the actual configured retract_length which  | ||||
|         might be 0, in which case the retraction logic gets skipped. */ | ||||
|     if (this->config.use_firmware_retraction) length = 1; | ||||
|     if (this->config.use_firmware_retraction) | ||||
|         length = 1; | ||||
|      | ||||
|     // If we use volumetric E values we turn lengths into volumes */
 | ||||
|     if (this->config.use_volumetric_e) { | ||||
|  | @ -533,52 +420,48 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std | |||
|         restart_extra = restart_extra * area; | ||||
|     } | ||||
|      | ||||
|     double dE = m_extruder->retract(length, restart_extra); | ||||
|     if (dE != 0) { | ||||
| 
 | ||||
|     std::string gcode; | ||||
|     if (double dE = m_extruder->retract(length, restart_extra);  dE != 0) { | ||||
|         if (this->config.use_firmware_retraction) { | ||||
|             if (FLAVOR_IS(gcfMachinekit)) | ||||
|                 gcode << "G22 ; retract\n"; | ||||
|             else | ||||
|                 gcode << "G10 ; retract\n"; | ||||
|             gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n"; | ||||
|         } else if (! m_extrusion_axis.empty()) { | ||||
|             gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E()) | ||||
|                            << " F" << XYZF_NUM(m_extruder->retract_speed() * 60.); | ||||
|             COMMENT(comment); | ||||
|             gcode << "\n"; | ||||
|             GCodeG1Formatter w; | ||||
|             w.emit_e(m_extrusion_axis, m_extruder->E()); | ||||
|             w.emit_f(m_extruder->retract_speed() * 60.); | ||||
|             w.emit_comment(this->config.gcode_comments, comment); | ||||
|             gcode = w.string(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     if (FLAVOR_IS(gcfMakerWare)) | ||||
|         gcode << "M103 ; extruder off\n"; | ||||
|      | ||||
|     return gcode.str(); | ||||
|         gcode += "M103 ; extruder off\n"; | ||||
| 
 | ||||
|     return gcode; | ||||
| } | ||||
| 
 | ||||
| std::string GCodeWriter::unretract() | ||||
| { | ||||
|     std::ostringstream gcode; | ||||
|     std::string gcode; | ||||
|      | ||||
|     if (FLAVOR_IS(gcfMakerWare)) | ||||
|         gcode << "M101 ; extruder on\n"; | ||||
|         gcode = "M101 ; extruder on\n"; | ||||
|      | ||||
|     double dE = m_extruder->unretract(); | ||||
|     if (dE != 0) { | ||||
|     if (double dE = m_extruder->unretract(); dE != 0) { | ||||
|         if (this->config.use_firmware_retraction) { | ||||
|             if (FLAVOR_IS(gcfMachinekit)) | ||||
|                  gcode << "G23 ; unretract\n"; | ||||
|             else | ||||
|                  gcode << "G11 ; unretract\n"; | ||||
|             gcode << this->reset_e(); | ||||
|             gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ; unretract\n" : "G11 ; unretract\n"; | ||||
|             gcode += this->reset_e(); | ||||
|         } else if (! m_extrusion_axis.empty()) { | ||||
|             // use G1 instead of G0 because G0 will blend the restart with the previous travel move
 | ||||
|             gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E()) | ||||
|                            << " F" << XYZF_NUM(m_extruder->deretract_speed() * 60.); | ||||
|             if (this->config.gcode_comments) gcode << " ; unretract"; | ||||
|             gcode << "\n"; | ||||
|             GCodeG1Formatter w; | ||||
|             w.emit_e(m_extrusion_axis, m_extruder->E()); | ||||
|             w.emit_f(m_extruder->deretract_speed() * 60.); | ||||
|             w.emit_comment(this->config.gcode_comments, " ; unretract"); | ||||
|             gcode += w.string(); | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return gcode.str(); | ||||
|     return gcode; | ||||
| } | ||||
| 
 | ||||
| /*  If this method is called more than once before calling unlift(),
 | ||||
|  | @ -611,4 +494,88 @@ std::string GCodeWriter::unlift() | |||
|     return gcode; | ||||
| } | ||||
| 
 | ||||
| std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed) | ||||
| { | ||||
|     std::ostringstream gcode; | ||||
|     if (speed == 0) { | ||||
|         switch (gcode_flavor) { | ||||
|         case gcfTeacup: | ||||
|             gcode << "M106 S0"; break; | ||||
|         case gcfMakerWare: | ||||
|         case gcfSailfish: | ||||
|             gcode << "M127";    break; | ||||
|         default: | ||||
|             gcode << "M107";    break; | ||||
|         } | ||||
|         if (gcode_comments) | ||||
|             gcode << " ; disable fan"; | ||||
|         gcode << "\n"; | ||||
|     } else { | ||||
|         switch (gcode_flavor) { | ||||
|         case gcfMakerWare: | ||||
|         case gcfSailfish: | ||||
|             gcode << "M126";    break; | ||||
|         case gcfMach3: | ||||
|         case gcfMachinekit: | ||||
|             gcode << "M106 P" << 255.0 * speed / 100.0; break; | ||||
|         default: | ||||
|             gcode << "M106 S" << 255.0 * speed / 100.0; break; | ||||
|         } | ||||
|         if (gcode_comments)  | ||||
|             gcode << " ; enable fan"; | ||||
|         gcode << "\n"; | ||||
|     } | ||||
|     return gcode.str(); | ||||
| } | ||||
| 
 | ||||
| std::string GCodeWriter::set_fan(unsigned int speed) const | ||||
| { | ||||
|     return GCodeWriter::set_fan(this->config.gcode_flavor, this->config.gcode_comments, speed); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GCodeFormatter::emit_axis(const char axis, const double v, size_t digits) { | ||||
|     assert(digits <= 6); | ||||
|     static constexpr const std::array<int, 7> pow_10{1, 10, 100, 1000, 10000, 100000, 1000000}; | ||||
|     *ptr_err.ptr++ = ' '; *ptr_err.ptr++ = axis; | ||||
| 
 | ||||
|     char *base_ptr = this->ptr_err.ptr; | ||||
|     auto  v_int    = int64_t(std::round(v * pow_10[digits])); | ||||
|     // Older stdlib on macOS doesn't support std::from_chars at all, so it is used boost::spirit::karma::generate instead of it.
 | ||||
|     // That is a little bit slower than std::to_chars but not much.
 | ||||
| #ifdef __APPLE__ | ||||
|     boost::spirit::karma::generate(this->ptr_err.ptr, boost::spirit::karma::int_generator<int64_t>(), v_int); | ||||
| #else | ||||
|     // this->buf_end minus 1 because we need space for adding the extra decimal point.
 | ||||
|     this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end - 1, v_int); | ||||
| #endif | ||||
|     size_t writen_digits = (this->ptr_err.ptr - base_ptr) - (v_int < 0 ? 1 : 0); | ||||
|     if (writen_digits < digits) { | ||||
|         // Number is smaller than 10^digits, so that we will pad it with zeros.
 | ||||
|         size_t remaining_digits = digits - writen_digits; | ||||
|         // Move all newly inserted chars by remaining_digits to allocate space for padding with zeros.
 | ||||
|         for (char *from_ptr = this->ptr_err.ptr - 1, *to_ptr = from_ptr + remaining_digits; from_ptr >= this->ptr_err.ptr - writen_digits; --to_ptr, --from_ptr) | ||||
|             *to_ptr = *from_ptr; | ||||
| 
 | ||||
|         memset(this->ptr_err.ptr - writen_digits, '0', remaining_digits); | ||||
|         this->ptr_err.ptr += remaining_digits; | ||||
|     } | ||||
| 
 | ||||
|     // Move all newly inserted chars by one to allocate space for a decimal point.
 | ||||
|     for (char *to_ptr = this->ptr_err.ptr, *from_ptr = to_ptr - 1; from_ptr >= this->ptr_err.ptr - digits; --to_ptr, --from_ptr) | ||||
|         *to_ptr = *from_ptr; | ||||
| 
 | ||||
|     *(this->ptr_err.ptr - digits) = '.'; | ||||
|     for (size_t i = 0; i < digits; ++i) { | ||||
|         if (*this->ptr_err.ptr != '0') | ||||
|             break; | ||||
|         this->ptr_err.ptr--; | ||||
|     } | ||||
|     if (*this->ptr_err.ptr == '.') | ||||
|         this->ptr_err.ptr--; | ||||
|     if ((this->ptr_err.ptr + 1) == base_ptr || *this->ptr_err.ptr == '-') | ||||
|         *(++this->ptr_err.ptr) = '0'; | ||||
|     this->ptr_err.ptr++; | ||||
| } | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| 
 | ||||
| #include "libslic3r.h" | ||||
| #include <string> | ||||
| #include <charconv> | ||||
| #include "Extruder.hpp" | ||||
| #include "Point.hpp" | ||||
| #include "PrintConfig.hpp" | ||||
|  | @ -18,7 +19,7 @@ public: | |||
|     GCodeWriter() :  | ||||
|         multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr), | ||||
|         m_single_extruder_multi_material(false), | ||||
|         m_last_acceleration(0), m_max_acceleration(0), m_last_fan_speed(0),  | ||||
|         m_last_acceleration(0), m_max_acceleration(0), | ||||
|         m_last_bed_temperature(0), m_last_bed_temperature_reached(true),  | ||||
|         m_lifted(0) | ||||
|         {} | ||||
|  | @ -42,7 +43,6 @@ public: | |||
|     std::string postamble() const; | ||||
|     std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const; | ||||
|     std::string set_bed_temperature(unsigned int temperature, bool wait = false); | ||||
|     std::string set_fan(unsigned int speed, bool dont_save = false); | ||||
|     std::string set_acceleration(unsigned int acceleration); | ||||
|     std::string reset_e(bool force = false); | ||||
|     std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const; | ||||
|  | @ -69,6 +69,12 @@ public: | |||
|     std::string unlift(); | ||||
|     Vec3d       get_position() const { return m_pos; } | ||||
| 
 | ||||
|     // To be called by the CoolingBuffer from another thread.
 | ||||
|     static std::string set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed); | ||||
|     // To be called by the main thread. It always emits the G-code, it does not remember the previous state.
 | ||||
|     // Keeping the state is left to the CoolingBuffer, which runs asynchronously on another thread.
 | ||||
|     std::string set_fan(unsigned int speed) const; | ||||
| 
 | ||||
| private: | ||||
| 	// Extruders are sorted by their ID, so that binary search is possible.
 | ||||
|     std::vector<Extruder> m_extruders; | ||||
|  | @ -79,7 +85,6 @@ private: | |||
|     // Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware.
 | ||||
|     // If set to zero, the limit is not in action.
 | ||||
|     unsigned int    m_max_acceleration; | ||||
|     unsigned int    m_last_fan_speed; | ||||
|     unsigned int    m_last_bed_temperature; | ||||
|     bool            m_last_bed_temperature_reached; | ||||
|     double          m_lifted; | ||||
|  | @ -89,6 +94,84 @@ private: | |||
|     std::string _retract(double length, double restart_extra, const std::string &comment); | ||||
| }; | ||||
| 
 | ||||
| class GCodeFormatter { | ||||
| public: | ||||
|     GCodeFormatter() { | ||||
|         this->buf_end = buf + buflen; | ||||
|         this->ptr_err.ptr = this->buf; | ||||
|     } | ||||
| 
 | ||||
|     GCodeFormatter(const GCodeFormatter&) = delete; | ||||
|     GCodeFormatter& operator=(const GCodeFormatter&) = delete; | ||||
| 
 | ||||
|     static constexpr const int XYZF_EXPORT_DIGITS = 3; | ||||
|     static constexpr const int E_EXPORT_DIGITS    = 5; | ||||
| 
 | ||||
|     void emit_axis(const char axis, const double v, size_t digits); | ||||
| 
 | ||||
|     void emit_xy(const Vec2d &point) { | ||||
|         this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); | ||||
|         this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); | ||||
|     } | ||||
| 
 | ||||
|     void emit_xyz(const Vec3d &point) { | ||||
|         this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS); | ||||
|         this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS); | ||||
|         this->emit_z(point.z()); | ||||
|     } | ||||
| 
 | ||||
|     void emit_z(const double z) { | ||||
|         this->emit_axis('Z', z, XYZF_EXPORT_DIGITS); | ||||
|     } | ||||
| 
 | ||||
|     void emit_e(const std::string &axis, double v) { | ||||
|         if (! axis.empty()) { | ||||
|             // not gcfNoExtrusion
 | ||||
|             this->emit_axis(axis[0], v, E_EXPORT_DIGITS); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void emit_f(double speed) { | ||||
|         this->emit_axis('F', speed, XYZF_EXPORT_DIGITS); | ||||
|     } | ||||
| 
 | ||||
|     void emit_string(const std::string &s) { | ||||
|         strncpy(ptr_err.ptr, s.c_str(), s.size()); | ||||
|         ptr_err.ptr += s.size(); | ||||
|     } | ||||
| 
 | ||||
|     void emit_comment(bool allow_comments, const std::string &comment) { | ||||
|         if (allow_comments && ! comment.empty()) { | ||||
|             *ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' '; | ||||
|             this->emit_string(comment); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     std::string string() { | ||||
|         *ptr_err.ptr ++ = '\n'; | ||||
|         return std::string(this->buf, ptr_err.ptr - buf); | ||||
|     } | ||||
| 
 | ||||
| protected: | ||||
|     static constexpr const size_t   buflen = 256; | ||||
|     char                            buf[buflen]; | ||||
|     char* buf_end; | ||||
|     std::to_chars_result            ptr_err; | ||||
| }; | ||||
| 
 | ||||
| class GCodeG1Formatter : public GCodeFormatter { | ||||
| public: | ||||
|     GCodeG1Formatter() { | ||||
|         this->buf[0] = 'G'; | ||||
|         this->buf[1] = '1'; | ||||
|         this->buf_end = buf + buflen; | ||||
|         this->ptr_err.ptr = this->buf + 2; | ||||
|     } | ||||
| 
 | ||||
|     GCodeG1Formatter(const GCodeG1Formatter&) = delete; | ||||
|     GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete; | ||||
| }; | ||||
| 
 | ||||
| } /* namespace Slic3r */ | ||||
| 
 | ||||
| #endif /* slic3r_GCodeWriter_hpp_ */ | ||||
|  |  | |||
|  | @ -33,7 +33,9 @@ public: | |||
|     void rotate(double angle, const Point ¢er); | ||||
|     void reverse() { std::reverse(this->points.begin(), this->points.end()); } | ||||
| 
 | ||||
|     const Point& first_point() const { return this->points.front(); } | ||||
|     const Point& front() const { return this->points.front(); } | ||||
|     const Point& back() const { return this->points.back(); } | ||||
|     const Point& first_point() const { return this->front(); } | ||||
|     virtual const Point& last_point() const = 0; | ||||
|     virtual Lines lines() const = 0; | ||||
|     size_t size() const { return points.size(); } | ||||
|  |  | |||
|  | @ -18,11 +18,6 @@ using ConstPolygonPtrs  = std::vector<const Polygon*>; | |||
| class Polygon : public MultiPoint | ||||
| { | ||||
| public: | ||||
|     explicit operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } | ||||
|     explicit operator Polyline() const { return this->split_at_first_point(); } | ||||
|     Point& operator[](Points::size_type idx) { return this->points[idx]; } | ||||
|     const Point& operator[](Points::size_type idx) const { return this->points[idx]; } | ||||
| 
 | ||||
|     Polygon() = default; | ||||
|     virtual ~Polygon() = default; | ||||
|     explicit Polygon(const Points &points) : MultiPoint(points) {} | ||||
|  | @ -39,6 +34,9 @@ public: | |||
|     Polygon& operator=(const Polygon &other) { points = other.points; return *this; } | ||||
|     Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; } | ||||
| 
 | ||||
|     Point& operator[](Points::size_type idx) { return this->points[idx]; } | ||||
|     const Point& operator[](Points::size_type idx) const { return this->points[idx]; } | ||||
| 
 | ||||
|     // last point == first point for polygons
 | ||||
|     const Point& last_point() const override { return this->points.front(); } | ||||
| 
 | ||||
|  |  | |||
|  | @ -10,20 +10,6 @@ | |||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| Polyline::operator Polylines() const | ||||
| { | ||||
|     Polylines polylines; | ||||
|     polylines.push_back(*this); | ||||
|     return polylines; | ||||
| } | ||||
| 
 | ||||
| Polyline::operator Line() const | ||||
| { | ||||
|     if (this->points.size() > 2)  | ||||
|         throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line"); | ||||
|     return Line(this->points.front(), this->points.back()); | ||||
| } | ||||
| 
 | ||||
| const Point& Polyline::leftmost_point() const | ||||
| { | ||||
|     const Point *p = &this->points.front(); | ||||
|  |  | |||
|  | @ -59,13 +59,11 @@ public: | |||
|             src.points.clear(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     explicit operator Polylines() const; | ||||
|     explicit operator Line() const; | ||||
|    | ||||
|     const Point& last_point() const override { return this->points.back(); } | ||||
| 
 | ||||
|     const Point& leftmost_point() const; | ||||
|     Lines lines() const override; | ||||
| 
 | ||||
|     void clip_end(double distance); | ||||
|     void clip_start(double distance); | ||||
|     void extend_end(double distance); | ||||
|  |  | |||
|  | @ -397,7 +397,7 @@ bool Print::sequential_print_horizontal_clearance_valid(const Print& print, Poly | |||
| 	        convex_hull.translate(instance.shift - print_object->center_offset()); | ||||
|             // if output needed, collect indices (inside convex_hulls_other) of intersecting hulls
 | ||||
|             for (size_t i = 0; i < convex_hulls_other.size(); ++i) { | ||||
|                 if (!intersection((Polygons)convex_hulls_other[i], (Polygons)convex_hull).empty()) { | ||||
|                 if (! intersection(convex_hulls_other[i], convex_hull).empty()) { | ||||
|                     if (polygons == nullptr) | ||||
|                         return false; | ||||
|                     else { | ||||
|  | @ -812,7 +812,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const | |||
| // Slicing process, running at a background thread.
 | ||||
| void Print::process() | ||||
| { | ||||
|     name_tbb_thread_pool_threads(); | ||||
|     name_tbb_thread_pool_threads_set_locale(); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); | ||||
|     for (PrintObject *obj : m_objects) | ||||
|  |  | |||
|  | @ -370,7 +370,7 @@ bool add_cavity(indexed_triangle_set &pad, | |||
| 
 | ||||
|     if (inner_base.empty() || middle_base.empty()) { logerr(); return false; } | ||||
| 
 | ||||
|     ExPolygons pdiff = diff_ex((Polygons)top_poly, (Polygons)middle_base.contour); | ||||
|     ExPolygons pdiff = diff_ex(top_poly, middle_base.contour); | ||||
| 
 | ||||
|     if (pdiff.size() != 1) { logerr(); return false; } | ||||
| 
 | ||||
|  |  | |||
|  | @ -693,7 +693,7 @@ void SLAPrint::process() | |||
|     if (m_objects.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     name_tbb_thread_pool_threads(); | ||||
|     name_tbb_thread_pool_threads_set_locale(); | ||||
| 
 | ||||
|     // Assumption: at this point the print objects should be populated only with
 | ||||
|     // the model objects we have to process and the instances are also filtered
 | ||||
|  |  | |||
|  | @ -3338,7 +3338,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const | |||
|                 Polygon     &contour = (i_contour == 0) ? it_contact_expoly->contour : it_contact_expoly->holes[i_contour - 1]; | ||||
|                 const Point *seg_current_pt = nullptr; | ||||
|                 coordf_t     seg_current_t  = 0.; | ||||
|                 if (! intersection_pl((Polylines)contour.split_at_first_point(), overhang_with_margin).empty()) { | ||||
|                 if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { | ||||
|                     // The contour is below the overhang at least to some extent.
 | ||||
|                     //FIXME ideally one would place the circles below the overhang only.
 | ||||
|                     // Walk around the contour and place circles so their centers are not closer than circle_distance from each other.
 | ||||
|  |  | |||
|  | @ -188,7 +188,8 @@ std::optional<std::string> get_current_thread_name() | |||
| #endif // _WIN32
 | ||||
| 
 | ||||
| // Spawn (n - 1) worker threads on Intel TBB thread pool and name them by an index and a system thread ID.
 | ||||
| void name_tbb_thread_pool_threads() | ||||
| // Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator.
 | ||||
| void name_tbb_thread_pool_threads_set_locale() | ||||
| { | ||||
| 	static bool initialized = false; | ||||
| 	if (initialized) | ||||
|  | @ -233,6 +234,21 @@ void name_tbb_thread_pool_threads() | |||
| 				std::ostringstream name; | ||||
| 		        name << "slic3r_tbb_" << range.begin(); | ||||
| 		        set_current_thread_name(name.str().c_str()); | ||||
| 		        // Set locales of the worker thread to "C".
 | ||||
| #ifdef _WIN32 | ||||
| 			    _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); | ||||
| 			    std::setlocale(LC_ALL, "C"); | ||||
| #else | ||||
| 				// We are leaking some memory here, because the newlocale() produced memory will never be released.
 | ||||
| 				// This is not a problem though, as there will be a maximum one worker thread created per physical thread.
 | ||||
| 				uselocale(newlocale( | ||||
| #ifdef __APPLE__ | ||||
| 					LC_ALL_MASK | ||||
| #else // some Unix / Linux / BSD
 | ||||
| 					LC_ALL | ||||
| #endif | ||||
| 					, "C", nullptr)); | ||||
| #endif | ||||
|     		} | ||||
|         }); | ||||
| } | ||||
|  |  | |||
|  | @ -33,7 +33,8 @@ std::optional<std::string> get_current_thread_name(); | |||
| 
 | ||||
| // To be called somewhere before the TBB threads are spinned for the first time, to
 | ||||
| // give them names recognizible in the debugger.
 | ||||
| void name_tbb_thread_pool_threads(); | ||||
| // Also it sets locale of the worker threads to "C" for the G-code generator to produce "." as a decimal separator.
 | ||||
| void name_tbb_thread_pool_threads_set_locale(); | ||||
| 
 | ||||
| template<class Fn> | ||||
| inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn) | ||||
|  |  | |||
|  | @ -92,7 +92,7 @@ void Bed_2D::repaint(const std::vector<Vec2d>& shape) | |||
| 	for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) { | ||||
| 		polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) })); | ||||
| 	} | ||||
| 	polylines = intersection_pl(polylines, (Polygons)bed_polygon); | ||||
| 	polylines = intersection_pl(polylines, bed_polygon); | ||||
| 
 | ||||
|     dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxPENSTYLE_SOLID)); | ||||
| 	for (auto pl : polylines) | ||||
|  |  | |||
|  | @ -409,6 +409,11 @@ GLVolume::GLVolume(float r, float g, float b, float a) | |||
|     set_render_color(color); | ||||
| } | ||||
| 
 | ||||
| void GLVolume::set_color(const std::array<float, 4>& rgba) | ||||
| { | ||||
|     color = rgba; | ||||
| } | ||||
| 
 | ||||
| void GLVolume::set_render_color(float r, float g, float b, float a) | ||||
| { | ||||
|     render_color = { r, g, b, a }; | ||||
|  | @ -458,8 +463,9 @@ void GLVolume::set_render_color() | |||
|         render_color[3] = color[3]; | ||||
| } | ||||
| 
 | ||||
| void GLVolume::set_color_from_model_volume(const ModelVolume& model_volume) | ||||
| std::array<float, 4> color_from_model_volume(const ModelVolume& model_volume) | ||||
| { | ||||
|     std::array<float, 4> color; | ||||
|     if (model_volume.is_negative_volume()) { | ||||
|         color[0] = 0.2f; | ||||
|         color[1] = 0.2f; | ||||
|  | @ -481,6 +487,7 @@ void GLVolume::set_color_from_model_volume(const ModelVolume& model_volume) | |||
|         color[2] = 1.0f; | ||||
|     } | ||||
|     color[3] = model_volume.is_model_part() ? 1.f : 0.5f; | ||||
|     return color; | ||||
| } | ||||
| 
 | ||||
| Transform3d GLVolume::world_matrix() const | ||||
|  | @ -635,7 +642,7 @@ int GLVolumeCollection::load_object_volume( | |||
|     color[3] = model_volume->is_model_part() ? 1.f : 0.5f; | ||||
|     this->volumes.emplace_back(new GLVolume(color)); | ||||
|     GLVolume& v = *this->volumes.back(); | ||||
|     v.set_color_from_model_volume(*model_volume); | ||||
|     v.set_color(color_from_model_volume(*model_volume)); | ||||
| #if ENABLE_SMOOTH_NORMALS | ||||
|     v.indexed_vertex_array.load_mesh(mesh, true); | ||||
| #else | ||||
|  |  | |||
|  | @ -39,6 +39,10 @@ class ModelObject; | |||
| class ModelVolume; | ||||
| enum ModelInstanceEPrintVolumeState : unsigned char; | ||||
| 
 | ||||
| // Return appropriate color based on the ModelVolume.
 | ||||
| std::array<float, 4> color_from_model_volume(const ModelVolume& model_volume); | ||||
| 
 | ||||
| 
 | ||||
| // A container for interleaved arrays of 3D vertices and normals,
 | ||||
| // possibly indexed by triangles and / or quads.
 | ||||
| class GLIndexedVertexArray { | ||||
|  | @ -393,6 +397,7 @@ public: | |||
|         return out; | ||||
|     } | ||||
| 
 | ||||
|     void set_color(const std::array<float, 4>& rgba); | ||||
|     void set_render_color(float r, float g, float b, float a); | ||||
|     void set_render_color(const std::array<float, 4>& rgba); | ||||
|     // Sets render color in dependence of current state
 | ||||
|  |  | |||
|  | @ -122,7 +122,9 @@ void CopyrightsDialog::fill_entries() | |||
|         { "AppImage packaging for Linux using AppImageKit" | ||||
|                             , "2004-2019 Simon Peter and contributors"      , "https://appimage.org/" }, | ||||
|         { "lib_fts" | ||||
|                             , "Forrest Smith"                               , "https://www.forrestthewoods.com/" } | ||||
|                             , "Forrest Smith"                               , "https://www.forrestthewoods.com/" }, | ||||
|         { "fast_float" | ||||
|                             , "Daniel Lemire, João Paulo Magalhaes and contributors", "https://github.com/fastfloat/fast_float" } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -209,8 +209,8 @@ void BackgroundSlicingProcess::process_sla() | |||
| 
 | ||||
| void BackgroundSlicingProcess::thread_proc() | ||||
| { | ||||
|     set_current_thread_name("slic3r_BgSlcPcs"); | ||||
| 	name_tbb_thread_pool_threads(); | ||||
| 	set_current_thread_name("slic3r_BgSlcPcs"); | ||||
|     name_tbb_thread_pool_threads_set_locale(); | ||||
| 
 | ||||
| 	assert(m_print != nullptr); | ||||
| 	assert(m_print == m_fff_print || m_print == m_sla_print); | ||||
|  |  | |||
|  | @ -171,23 +171,14 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con | |||
|                                         "- Detect bridging perimeters")); | ||||
|                 if (is_global_config) | ||||
|                     msg_text += "\n\n" + _(L("Shall I adjust those settings for supports?")); | ||||
|                 //wxMessageDialog dialog(nullptr, msg_text, _(L("Support Generator")),
 | ||||
|                 MessageDialog dialog(nullptr, msg_text, _(L("Support Generator")), | ||||
|                                        wxICON_WARNING | (is_global_config ? wxYES | wxNO | wxCANCEL : wxOK)); | ||||
|                 MessageDialog dialog(nullptr, msg_text, _L("Support Generator"), wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); | ||||
|                 DynamicPrintConfig new_conf = *config; | ||||
|                 auto answer = dialog.ShowModal(); | ||||
|                 if (!is_global_config || answer == wxID_YES) { | ||||
|                     // Enable "detect bridging perimeters".
 | ||||
|                     new_conf.set_key_value("overhangs", new ConfigOptionBool(true)); | ||||
|                 } | ||||
|                 else if (answer == wxID_NO) { | ||||
|                     // Do nothing, leave supports on and "detect bridging perimeters" off.
 | ||||
|                 } | ||||
|                 else if (answer == wxID_CANCEL) { | ||||
|                     // Disable supports.
 | ||||
|                     new_conf.set_key_value("support_material", new ConfigOptionBool(false)); | ||||
|                     support_material_overhangs_queried = false; | ||||
|                 } | ||||
|                 //else Do nothing, leave supports on and "detect bridging perimeters" off.
 | ||||
|                 apply(config, &new_conf); | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -197,6 +197,17 @@ wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRec | |||
|         labelRect.SetWidth(labelRect.GetWidth() - bmp_width); | ||||
|     } | ||||
| 
 | ||||
| #ifdef __WXMSW__ | ||||
|     // Case when from some reason we try to create next EditorCtrl till old one was not deleted
 | ||||
|     if (auto children = parent->GetChildren(); children.GetCount() > 0) | ||||
|         for (auto child : children) | ||||
|             if (dynamic_cast<wxTextCtrl*>(child)) { | ||||
|                 parent->RemoveChild(child); | ||||
|                 child->Destroy(); | ||||
|                 break; | ||||
|             } | ||||
| #endif // __WXMSW__
 | ||||
| 
 | ||||
|     wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), | ||||
|                                              position, labelRect.GetSize(), wxTE_PROCESS_ENTER); | ||||
|     text_editor->SetInsertionPointEnd(); | ||||
|  |  | |||
|  | @ -284,7 +284,7 @@ void GCodeViewer::SequentialView::Marker::render() const | |||
|     ImGui::PopStyleVar(); | ||||
| } | ||||
| 
 | ||||
| void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, const std::vector<size_t> &lines_ends) | ||||
| void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& filename, std::vector<size_t> &&lines_ends) | ||||
| { | ||||
|     assert(! m_file.is_open()); | ||||
|     if (m_file.is_open()) | ||||
|  | @ -563,6 +563,8 @@ GCodeViewer::GCodeViewer() | |||
|     set_toolpath_move_type_visible(EMoveType::Extrude, true); | ||||
| #endif // !ENABLE_SEAMS_USING_MODELS
 | ||||
| 
 | ||||
|     m_extrusions.reset_role_visibility_flags(); | ||||
| 
 | ||||
| //    m_sequential_view.skip_invisible_moves = true;
 | ||||
| } | ||||
| 
 | ||||
|  | @ -577,7 +579,9 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& | |||
|     // release gpu memory, if used
 | ||||
|     reset();  | ||||
| 
 | ||||
|     m_sequential_view.gcode_window.load_gcode(gcode_result.filename, gcode_result.lines_ends); | ||||
|     m_sequential_view.gcode_window.load_gcode(gcode_result.filename, | ||||
|         // Stealing out lines_ends should be safe because this gcode_result is processed only once (see the 1st if in this function).
 | ||||
|         std::move(const_cast<std::vector<size_t>&>(gcode_result.lines_ends))); | ||||
| 
 | ||||
| #if ENABLE_FIX_IMPORTING_COLOR_PRINT_VIEW_INTO_GCODEVIEWER | ||||
|     if (wxGetApp().is_gcode_viewer()) | ||||
|  | @ -734,7 +738,6 @@ void GCodeViewer::reset() | |||
|     m_extruder_ids = std::vector<unsigned char>(); | ||||
|     m_filament_diameters = std::vector<float>(); | ||||
|     m_filament_densities = std::vector<float>(); | ||||
|     m_extrusions.reset_role_visibility_flags(); | ||||
|     m_extrusions.reset_ranges(); | ||||
|     m_shells.volumes.clear(); | ||||
|     m_layers.reset(); | ||||
|  |  | |||
|  | @ -702,7 +702,7 @@ public: | |||
|         public: | ||||
|             GCodeWindow() = default; | ||||
|             ~GCodeWindow() { stop_mapping_file(); } | ||||
|             void load_gcode(const std::string& filename, const std::vector<size_t> &lines_ends); | ||||
|             void load_gcode(const std::string& filename, std::vector<size_t> &&lines_ends); | ||||
|             void reset() { | ||||
|                 stop_mapping_file(); | ||||
|                 m_lines_ends.clear(); | ||||
|  |  | |||
|  | @ -1845,7 +1845,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
|                     volume->extruder_id = extruder_id; | ||||
| 
 | ||||
|                 volume->is_modifier = !mvs->model_volume->is_model_part(); | ||||
|                 volume->set_color_from_model_volume(*mvs->model_volume); | ||||
|                 volume->set_color(color_from_model_volume(*mvs->model_volume)); | ||||
| 
 | ||||
|                 // updates volumes transformations
 | ||||
|                 volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation()); | ||||
|  | @ -2092,7 +2092,7 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& | |||
| 	vol_old.finalize_geometry(gl_initialized); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) | ||||
| void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors) | ||||
| { | ||||
|     m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); | ||||
| 
 | ||||
|  | @ -2100,10 +2100,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) | |||
|         m_gcode_viewer.update_shells_color_by_extruder(m_config); | ||||
|         _set_warning_notification_if_needed(EWarning::ToolpathOutside); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors) | ||||
| { | ||||
|     m_gcode_viewer.refresh(gcode_result, str_tool_colors); | ||||
|     set_as_dirty(); | ||||
|     request_extra_frame(); | ||||
|  |  | |||
|  | @ -722,8 +722,7 @@ public: | |||
| 
 | ||||
|     void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false); | ||||
| 
 | ||||
|     void load_gcode_preview(const GCodeProcessor::Result& gcode_result); | ||||
|     void refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors); | ||||
|     void load_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors); | ||||
|     void refresh_gcode_preview_render_paths(); | ||||
|     void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); } | ||||
|     GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); } | ||||
|  |  | |||
|  | @ -383,6 +383,7 @@ void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, cons | |||
| 
 | ||||
| 		// the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3
 | ||||
| //		comboCtrl->EnablePopupAnimation(false);
 | ||||
| 		popup->SetFont(comboCtrl->GetFont()); | ||||
| 		comboCtrl->SetPopupControl(popup); | ||||
| 		wxString title = from_u8(text); | ||||
| 		max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x); | ||||
|  |  | |||
|  | @ -958,6 +958,8 @@ bool GUI_App::on_init_inner() | |||
| //     update_mode(); // !!! do that later
 | ||||
|     SetTopWindow(mainframe); | ||||
| 
 | ||||
|     plater_->init_notification_manager(); | ||||
| 
 | ||||
|     m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); | ||||
| 
 | ||||
|     if (is_gcode_viewer()) { | ||||
|  | @ -2278,7 +2280,7 @@ wxBookCtrlBase* GUI_App::tab_panel() const | |||
|     return mainframe->m_tabpanel; | ||||
| } | ||||
| 
 | ||||
| NotificationManager* GUI_App::notification_manager()  | ||||
| std::shared_ptr<NotificationManager> GUI_App::notification_manager()  | ||||
| { | ||||
|     return plater_->get_notification_manager(); | ||||
| } | ||||
|  |  | |||
|  | @ -276,7 +276,7 @@ public: | |||
|     ObjectLayers*        obj_layers(); | ||||
|     Plater*              plater(); | ||||
|     Model&      		 model(); | ||||
|     NotificationManager* notification_manager(); | ||||
|     std::shared_ptr<NotificationManager> notification_manager(); | ||||
| 
 | ||||
|     // Parameters extracted from the command line to be passed to GUI after initialization.
 | ||||
|     GUI_InitParams* init_params { nullptr }; | ||||
|  |  | |||
|  | @ -16,6 +16,10 @@ | |||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include "slic3r/Utils/FixModelByWin10.hpp" | ||||
| #ifdef __APPLE__ | ||||
| #include "wx/dcclient.h" | ||||
| #include "slic3r/Utils/MacDarkMode.hpp" | ||||
| #endif | ||||
| 
 | ||||
| namespace Slic3r | ||||
| { | ||||
|  | @ -211,7 +215,6 @@ static int GetSelectedChoices(  wxArrayInt& selections, | |||
|                                 const wxString& caption, | ||||
|                                 const wxArrayString& choices) | ||||
| { | ||||
| #ifdef _WIN32 | ||||
|     wxMultiChoiceDialog dialog(nullptr, message, caption, choices); | ||||
|     wxGetApp().UpdateDlgDarkUI(&dialog); | ||||
| 
 | ||||
|  | @ -219,6 +222,39 @@ static int GetSelectedChoices(  wxArrayInt& selections, | |||
|     // deselects the first item which is selected by default
 | ||||
|     dialog.SetSelections(selections); | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
|     // Improvements for ChoiceListBox: Height of control will restect to items count
 | ||||
|     for (auto child : dialog.GetChildren()) | ||||
|         if (dynamic_cast<wxListBox*>(child) && !choices.IsEmpty()) { | ||||
|             wxClientDC dc(child); | ||||
| 
 | ||||
|             int height = dc.GetTextExtent(choices[0]).y; | ||||
|             int width = 0; | ||||
|             for (const auto& string : choices) | ||||
|                 width = std::max(width, dc.GetTextExtent(string).x); | ||||
| 
 | ||||
|             // calculate best size of ListBox
 | ||||
|             height += 3 * mac_max_scaling_factor(); // extend height by margins
 | ||||
|             width += 3 * height;                   // extend width by checkbox width and margins
 | ||||
| 
 | ||||
|             // don't make the listbox too tall (limit height to around 10 items)
 | ||||
|             // but don't make it too small neither
 | ||||
|             int list_height = wxMax(height * wxMin(wxMax(choices.Count(), 3), 10), 70); | ||||
|             wxSize sz_best = wxSize(width, list_height); | ||||
| 
 | ||||
|             wxSize sz = child->GetSize(); | ||||
|             child->SetMinSize(sz_best); | ||||
| 
 | ||||
|             // extend Dialog size, if calculated best size of ListBox is bigger then its size
 | ||||
|             wxSize dlg_sz = dialog.GetSize(); | ||||
|             if (int delta_x = sz_best.x - sz.x; delta_x > 0) dlg_sz.x += delta_x; | ||||
|             if (int delta_y = sz_best.y - sz.y; delta_y > 0) dlg_sz.y += delta_y; | ||||
|             dialog.SetSize(dlg_sz); | ||||
| 
 | ||||
|             break; | ||||
|         } | ||||
| #endif | ||||
| 
 | ||||
|     if (dialog.ShowModal() != wxID_OK) | ||||
|     { | ||||
|         // NB: intentionally do not clear the selections array here, the caller
 | ||||
|  | @ -229,9 +265,6 @@ static int GetSelectedChoices(  wxArrayInt& selections, | |||
| 
 | ||||
|     selections = dialog.GetSelections(); | ||||
|     return static_cast<int>(selections.GetCount()); | ||||
| #else | ||||
|     return wxGetSelectedChoices(selections, message, caption, choices); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static wxMenu* create_settings_popupmenu(wxMenu* parent_menu, const bool is_object_settings, wxDataViewItem item/*, ModelConfig& config*/) | ||||
|  |  | |||
|  | @ -734,9 +734,9 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee | |||
| 
 | ||||
|             double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); | ||||
|             if( bottom_area - top_area > delta_area) { | ||||
|                 NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager(); | ||||
|                 std::shared_ptr<NotificationManager> notif_mngr = wxGetApp().plater()->get_notification_manager(); | ||||
|                 notif_mngr->push_notification( | ||||
|                     NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotification, | ||||
|                     NotificationType::SignDetected, NotificationManager::NotificationLevel::RegularNotificationLevel, | ||||
|                     _u8L("NOTE:") + "\n" + _u8L("Sliced object looks like the sign") + "\n", | ||||
|                     _u8L("Apply auto color change to print"), | ||||
|                     [this](wxEvtHandler*) { | ||||
|  | @ -943,8 +943,7 @@ void Preview::load_print_as_fff(bool keep_z_range) | |||
|         m_canvas->set_selected_extruder(0); | ||||
|         if (gcode_preview_data_valid) { | ||||
|             // Load the real G-code preview.
 | ||||
|             m_canvas->load_gcode_preview(*m_gcode_result); | ||||
|             m_canvas->refresh_gcode_preview(*m_gcode_result, colors); | ||||
|             m_canvas->load_gcode_preview(*m_gcode_result, colors); | ||||
|             m_left_sizer->Show(m_bottom_toolbar_panel); | ||||
|             m_left_sizer->Layout(); | ||||
|             Refresh(); | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ static inline void show_notification_extruders_limit_exceeded() | |||
|     wxGetApp() | ||||
|         .plater() | ||||
|         ->get_notification_manager() | ||||
|         ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotification, | ||||
|         ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotificationLevel, | ||||
|                             GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the " | ||||
|                                            "first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)); | ||||
| } | ||||
|  |  | |||
|  | @ -337,7 +337,7 @@ void GLGizmoSimplify::on_set_state() | |||
|             auto notification_manager = wxGetApp().plater()->get_notification_manager(); | ||||
|             notification_manager->push_notification( | ||||
|                 NotificationType::CustomNotification, | ||||
|                 NotificationManager::NotificationLevel::RegularNotification, | ||||
|                 NotificationManager::NotificationLevel::RegularNotificationLevel, | ||||
|                 _u8L("ERROR: Wait until Simplification ends or Cancel process.")); | ||||
|             return; | ||||
|         } | ||||
|  |  | |||
|  | @ -152,7 +152,7 @@ void InstancesHider::on_update() | |||
|         canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst); | ||||
|         canvas->set_use_clipping_planes(true); | ||||
|         // Some objects may be sinking, do not show whatever is below the bed.
 | ||||
|         canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), 0.)); | ||||
|         canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); | ||||
|         canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits<double>::max())); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -164,12 +164,7 @@ void InstancesHider::on_update() | |||
|             m_clippers.clear(); | ||||
|             for (const TriangleMesh* mesh : meshes) { | ||||
|                 m_clippers.emplace_back(new MeshClipper); | ||||
|                 if (mo->get_instance_min_z(active_inst) < SINKING_Z_THRESHOLD) | ||||
|                     m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), 0.)); | ||||
|                 else { | ||||
|                     m_clippers.back()->set_plane(ClippingPlane::ClipsNothing()); | ||||
|                     m_clippers.back()->set_limiting_plane(ClippingPlane::ClipsNothing()); | ||||
|                 } | ||||
|                 m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); | ||||
|                 m_clippers.back()->set_mesh(*mesh); | ||||
|             } | ||||
|             m_old_meshes = meshes; | ||||
|  | @ -218,8 +213,16 @@ void InstancesHider::render_cut() const | |||
|             clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); | ||||
| 
 | ||||
|         glsafe(::glPushMatrix()); | ||||
|         glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); | ||||
|         if (mv->is_model_part()) | ||||
|             glsafe(::glColor3f(0.8f, 0.3f, 0.0f)); | ||||
|         else { | ||||
|             const std::array<float, 4>& c = color_from_model_volume(*mv); | ||||
|             glsafe(::glColor4f(c[0], c[1], c[2], c[3])); | ||||
|         } | ||||
|         glsafe(::glPushAttrib(GL_DEPTH_TEST)); | ||||
|         glsafe(::glDisable(GL_DEPTH_TEST)); | ||||
|         clipper->render_cut(); | ||||
|         glsafe(::glPopAttrib()); | ||||
|         glsafe(::glPopMatrix()); | ||||
| 
 | ||||
|         ++clipper_id; | ||||
|  | @ -385,8 +388,6 @@ void ObjectClipper::on_update() | |||
| 
 | ||||
|         m_active_inst_bb_radius = | ||||
|             mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius(); | ||||
|         //if (has_hollowed && m_clp_ratio != 0.)
 | ||||
|         //    m_clp_ratio = 0.25;
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -407,7 +408,6 @@ void ObjectClipper::render_cut() const | |||
|     const SelectionInfo* sel_info = get_pool()->selection_info(); | ||||
|     const ModelObject* mo = sel_info->model_object(); | ||||
|     Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation(); | ||||
|     const bool sinking = mo->bounding_box().min.z() < SINKING_Z_THRESHOLD; | ||||
| 
 | ||||
|     size_t clipper_id = 0; | ||||
|     for (const ModelVolume* mv : mo->volumes) { | ||||
|  | @ -418,9 +418,7 @@ void ObjectClipper::render_cut() const | |||
|         auto& clipper = m_clippers[clipper_id]; | ||||
|         clipper->set_plane(*m_clp); | ||||
|         clipper->set_transformation(trafo); | ||||
|         clipper->set_limiting_plane(sinking ? | ||||
|                                     ClippingPlane(Vec3d::UnitZ(), 0.) | ||||
|                                   : ClippingPlane::ClipsNothing()); | ||||
|         clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD)); | ||||
|         glsafe(::glPushMatrix()); | ||||
|         glsafe(::glColor3f(1.0f, 0.37f, 0.0f)); | ||||
|         clipper->render_cut(); | ||||
|  |  | |||
|  | @ -194,7 +194,7 @@ bool GLGizmosManager::check_gizmos_closed_except(EType type) const | |||
|     if (get_current_type() != type && get_current_type() != Undefined) { | ||||
|         wxGetApp().plater()->get_notification_manager()->push_notification( | ||||
|                     NotificationType::CustomSupportsAndSeamRemovedAfterRepair, | ||||
|                     NotificationManager::NotificationLevel::RegularNotification, | ||||
|                     NotificationManager::NotificationLevel::RegularNotificationLevel, | ||||
|                     _u8L("ERROR: Please close all manipulators available from " | ||||
|                          "the left toolbar first")); | ||||
|         return false; | ||||
|  | @ -1256,7 +1256,7 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const | |||
|     if (error_notification) | ||||
|         wxGetApp().plater()->get_notification_manager()->push_notification( | ||||
|                     NotificationType::QuitSLAManualMode, | ||||
|                     NotificationManager::NotificationLevel::ErrorNotification, | ||||
|                     NotificationManager::NotificationLevel::ErrorNotificationLevel, | ||||
|                     _u8L("You are currently editing SLA support points. Please, " | ||||
|                          "apply or discard your changes first.")); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1012,7 +1012,7 @@ void NotificationManager::HintNotification::retrieve_data(bool new_hint/* = true | |||
| 	if(hint_data != nullptr) | ||||
|     { | ||||
|         NotificationData nd { NotificationType::DidYouKnowHint, | ||||
| 						      NotificationLevel::RegularNotification, | ||||
| 						      NotificationLevel::RegularNotificationLevel, | ||||
| 							  0, | ||||
| 						      hint_data->text, | ||||
| 							  hint_data->hypertext, nullptr, | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ namespace Slic3r { | |||
| class ModelInstance; | ||||
| 
 | ||||
| namespace GUI { | ||||
| class NotificationManager; | ||||
| 
 | ||||
| class ArrangeJob : public PlaterJob | ||||
| { | ||||
|  | @ -39,8 +40,8 @@ protected: | |||
|     void process() override; | ||||
| 
 | ||||
| public: | ||||
|     ArrangeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) | ||||
|         : PlaterJob{std::move(pri), plater} | ||||
|     ArrangeJob(std::shared_ptr<NotificationManager> nm, Plater *plater) | ||||
|         : PlaterJob{nm, plater} | ||||
|     {} | ||||
|      | ||||
|     int status_range() const override | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| namespace Slic3r { namespace GUI { | ||||
| 
 | ||||
| class Plater; | ||||
| class NotificationManager; | ||||
| 
 | ||||
| class FillBedJob : public PlaterJob | ||||
| { | ||||
|  | @ -27,8 +28,8 @@ protected: | |||
|     void process() override; | ||||
| 
 | ||||
| public: | ||||
|     FillBedJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) | ||||
|         : PlaterJob{std::move(pri), plater} | ||||
|     FillBedJob(std::shared_ptr<NotificationManager> nm, Plater *plater) | ||||
|         : PlaterJob{nm, plater} | ||||
|     {} | ||||
| 
 | ||||
|     int status_range() const override | ||||
|  |  | |||
|  | @ -2,9 +2,11 @@ | |||
| #include <exception> | ||||
| 
 | ||||
| #include "Job.hpp" | ||||
| #include "../NotificationManager.hpp" | ||||
| #include <libslic3r/Thread.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| 
 | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| 
 | ||||
| void GUI::Job::run(std::exception_ptr &eptr) | ||||
|  | @ -30,8 +32,8 @@ void GUI::Job::update_status(int st, const wxString &msg) | |||
|     wxQueueEvent(this, evt); | ||||
| } | ||||
| 
 | ||||
| GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri) | ||||
|     : m_progress(std::move(pri)) | ||||
| GUI::Job::Job(std::shared_ptr<NotificationManager> nm) | ||||
|     : m_notifications(nm) | ||||
| { | ||||
|     m_thread_evt_id = wxNewId(); | ||||
| 
 | ||||
|  | @ -40,21 +42,21 @@ GUI::Job::Job(std::shared_ptr<ProgressIndicator> pri) | |||
| 
 | ||||
|         auto msg = evt.GetString(); | ||||
|         if (!msg.empty() && !m_worker_error) | ||||
|             m_progress->set_status_text(msg.ToUTF8().data()); | ||||
|             m_notifications->progress_indicator_set_status_text(msg.ToUTF8().data()); | ||||
| 
 | ||||
|         if (m_finalized) return; | ||||
| 
 | ||||
|         m_progress->set_progress(evt.GetInt()); | ||||
|         m_notifications->progress_indicator_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); | ||||
|             m_progress->set_cancel_callback(); | ||||
|             m_notifications->progress_indicator_set_range(m_range); | ||||
|             m_notifications->progress_indicator_set_cancel_callback(); | ||||
|             wxEndBusyCursor(); | ||||
|              | ||||
|             if (m_worker_error) { | ||||
|                 m_finalized = true; | ||||
|                 m_progress->set_status_text(""); | ||||
|                 m_progress->set_progress(m_range); | ||||
|                 m_notifications->progress_indicator_set_status_text(""); | ||||
|                 m_notifications->progress_indicator_set_progress(m_range); | ||||
|                 on_exception(m_worker_error); | ||||
|             } | ||||
|             else { | ||||
|  | @ -86,12 +88,12 @@ void GUI::Job::start() | |||
|         prepare(); | ||||
|          | ||||
|         // Save the current status indicatior range and push the new one
 | ||||
|         m_range = m_progress->get_range(); | ||||
|         m_progress->set_range(status_range()); | ||||
|         m_range = m_notifications->progress_indicator_get_range(); | ||||
|         m_notifications->progress_indicator_set_range(status_range()); | ||||
|          | ||||
|         // init cancellation flag and set the cancel callback
 | ||||
|         m_canceled.store(false); | ||||
|         m_progress->set_cancel_callback( | ||||
|         m_notifications->progress_indicator_set_cancel_callback( | ||||
|                     [this]() { m_canceled.store(true); }); | ||||
|          | ||||
|         m_finalized  = false; | ||||
|  |  | |||
|  | @ -8,14 +8,13 @@ | |||
| 
 | ||||
| #include <slic3r/GUI/I18N.hpp> | ||||
| 
 | ||||
| #include "ProgressIndicator.hpp" | ||||
| 
 | ||||
| #include <wx/event.h> | ||||
| 
 | ||||
| #include <boost/thread.hpp> | ||||
| 
 | ||||
| namespace Slic3r { namespace GUI { | ||||
| 
 | ||||
| class NotificationManager; | ||||
| // 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
 | ||||
|  | @ -33,7 +32,7 @@ class Job : public wxEvtHandler | |||
|     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::shared_ptr<NotificationManager>               m_notifications; | ||||
|     std::exception_ptr                 m_worker_error = nullptr; | ||||
|      | ||||
|     void run(std::exception_ptr &); | ||||
|  | @ -65,7 +64,7 @@ protected: | |||
|     } | ||||
|     | ||||
| public: | ||||
|     Job(std::shared_ptr<ProgressIndicator> pri); | ||||
|     Job(std::shared_ptr<NotificationManager> nm); | ||||
|      | ||||
|     bool is_finalized() const { return m_finalized; } | ||||
|      | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| namespace Slic3r { namespace GUI { | ||||
| 
 | ||||
| class Plater; | ||||
| class NotificationManager; | ||||
| 
 | ||||
| class PlaterJob : public Job { | ||||
| protected: | ||||
|  | @ -15,8 +16,8 @@ protected: | |||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     PlaterJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater): | ||||
|         Job{std::move(pri)}, m_plater{plater} {} | ||||
|     PlaterJob(std::shared_ptr<NotificationManager> nm, Plater *plater): | ||||
|         Job{nm}, m_plater{plater} {} | ||||
| }; | ||||
| 
 | ||||
| }} // namespace Slic3r::GUI
 | ||||
|  |  | |||
|  | @ -10,6 +10,8 @@ namespace Slic3r { | |||
| 
 | ||||
| namespace GUI { | ||||
| 
 | ||||
| class NotificationManager; | ||||
| 
 | ||||
| class RotoptimizeJob : public PlaterJob | ||||
| { | ||||
|     using FindFn = std::function<Vec2d(const ModelObject &           mo, | ||||
|  | @ -52,8 +54,8 @@ protected: | |||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     RotoptimizeJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) | ||||
|         : PlaterJob{std::move(pri), plater} | ||||
|     RotoptimizeJob(std::shared_ptr<NotificationManager> nm, Plater *plater) | ||||
|         : PlaterJob{nm, plater} | ||||
|     {} | ||||
| 
 | ||||
|     void finalize() override; | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/Plater.hpp" | ||||
| #include "slic3r/GUI/GUI_ObjectList.hpp" | ||||
| #include "slic3r/GUI/NotificationManager.hpp" | ||||
| 
 | ||||
| #include "libslic3r/Model.hpp" | ||||
| #include "libslic3r/PresetBundle.hpp" | ||||
|  | @ -124,8 +125,8 @@ public: | |||
|     priv(Plater *plt) : plater{plt} {} | ||||
| }; | ||||
| 
 | ||||
| SLAImportJob::SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater) | ||||
|     : PlaterJob{std::move(pri), plater}, p{std::make_unique<priv>(plater)} | ||||
| SLAImportJob::SLAImportJob(std::shared_ptr<NotificationManager> nm, Plater *plater) | ||||
|     : PlaterJob{nm, plater}, p{std::make_unique<priv>(plater)} | ||||
| {} | ||||
| 
 | ||||
| SLAImportJob::~SLAImportJob() = default; | ||||
|  |  | |||
|  | @ -5,6 +5,8 @@ | |||
| 
 | ||||
| namespace Slic3r { namespace GUI { | ||||
| 
 | ||||
| class NotificationManager; | ||||
| 
 | ||||
| class SLAImportJob : public PlaterJob { | ||||
|     class priv; | ||||
|      | ||||
|  | @ -16,7 +18,7 @@ protected: | |||
|     void finalize() override; | ||||
| 
 | ||||
| public: | ||||
|     SLAImportJob(std::shared_ptr<ProgressIndicator> pri, Plater *plater); | ||||
|     SLAImportJob(std::shared_ptr<NotificationManager> nm, Plater *plater); | ||||
|     ~SLAImportJob(); | ||||
| 
 | ||||
|     void reset(); | ||||
|  |  | |||
|  | @ -155,13 +155,13 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S | |||
|     SetIcon(main_frame_icon(wxGetApp().get_app_mode())); | ||||
| 
 | ||||
| 	// initialize status bar
 | ||||
|     m_statusbar = std::make_shared<ProgressStatusBar>(this); | ||||
|     m_statusbar->set_font(GUI::wxGetApp().normal_font()); | ||||
|     if (wxGetApp().is_editor()) | ||||
|         m_statusbar->embed(this); | ||||
|     m_statusbar->set_status_text(_L("Version") + " " + | ||||
|         SLIC3R_VERSION + " - " + | ||||
|         _L("Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")); | ||||
| //    m_statusbar = std::make_shared<ProgressStatusBar>(this);
 | ||||
| //    m_statusbar->set_font(GUI::wxGetApp().normal_font());
 | ||||
| //    if (wxGetApp().is_editor())
 | ||||
| //        m_statusbar->embed(this);
 | ||||
| //    m_statusbar->set_status_text(_L("Version") + " " +
 | ||||
| //        SLIC3R_VERSION + " - " +
 | ||||
| //       _L("Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases"));
 | ||||
| 
 | ||||
|     // initialize tabpanel and menubar
 | ||||
|     init_tabpanel(); | ||||
|  | @ -1033,7 +1033,7 @@ void MainFrame::on_sys_color_changed() | |||
|     wxGetApp().init_label_colours(); | ||||
| #ifdef __WXMSW__ | ||||
|     wxGetApp().UpdateDarkUI(m_tabpanel); | ||||
|     m_statusbar->update_dark_ui(); | ||||
|  //   m_statusbar->update_dark_ui();
 | ||||
| #ifdef _MSW_DARK_MODE | ||||
|     // update common mode sizer
 | ||||
|     if (!wxGetApp().tabs_as_menu()) | ||||
|  |  | |||
|  | @ -204,7 +204,7 @@ public: | |||
|     wxWindow*             m_plater_page{ nullptr }; | ||||
|     wxProgressDialog*     m_progress_dialog { nullptr }; | ||||
|     PrintHostQueueDialog* m_printhost_queue_dlg; | ||||
|     std::shared_ptr<ProgressStatusBar>  m_statusbar; | ||||
| //    std::shared_ptr<ProgressStatusBar>  m_statusbar;
 | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
|     std::unique_ptr<wxTaskBarIcon> m_taskbar_icon; | ||||
|  |  | |||
|  | @ -33,32 +33,32 @@ wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClic | |||
| wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); | ||||
| 
 | ||||
| const NotificationManager::NotificationData NotificationManager::basic_notifications[] = { | ||||
| 	{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10,  _u8L("3D Mouse disconnected.") }, | ||||
| 	{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotification, 20,  _u8L("Configuration update is available."),  _u8L("See more."), | ||||
| 	{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotificationLevel, 10,  _u8L("3D Mouse disconnected.") }, | ||||
| 	{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotificationLevel, 20,  _u8L("Configuration update is available."),  _u8L("See more."), | ||||
| 		[](wxEvtHandler* evnthndlr) { | ||||
| 			if (evnthndlr != nullptr) | ||||
| 				wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED)); | ||||
| 			return true; | ||||
| 		} | ||||
| 	}, | ||||
| 	{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20,  _u8L("New version is available."),  _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { | ||||
| 	{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20,  _u8L("New version is available."),  _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) { | ||||
| 		wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }}, | ||||
| 	{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotification, 10, | ||||
| 	{NotificationType::EmptyColorChangeCode, NotificationLevel::RegularNotificationLevel, 10, | ||||
| 		_u8L("You have just added a G-code for color change, but its value is empty.\n" | ||||
| 			 "To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") }, | ||||
| 	{NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotification, 10, | ||||
| 	{NotificationType::EmptyAutoColorChange, NotificationLevel::RegularNotificationLevel, 10, | ||||
| 		_u8L("No color change event was added to the print. The print does not look like a sign.") }, | ||||
| 	{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, | ||||
| 	{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10, | ||||
| 		_u8L("Desktop integration was successful.") }, | ||||
| 	{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotification, 10, | ||||
| 	{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, | ||||
| 		_u8L("Desktop integration failed.") }, | ||||
| 	{NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotification, 10, | ||||
| 	{NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10, | ||||
| 		_u8L("Undo desktop integration was successful.") }, | ||||
| 	{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotification, 10, | ||||
| 	{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10, | ||||
| 		_u8L("Undo desktop integration failed.") }, | ||||
| 	//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotification, 20,  _u8L("New vesion of PrusaSlicer is available.",  _u8L("Download page.") },
 | ||||
| 	//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20,  _u8L("Loading of model has Failed") },
 | ||||
| 	//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10,  _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
 | ||||
| 	//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20,  _u8L("New vesion of PrusaSlicer is available.",  _u8L("Download page.") },
 | ||||
| 	//{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20,  _u8L("Loading of model has Failed") },
 | ||||
| 	//{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10,  _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
 | ||||
| }; | ||||
| 
 | ||||
| namespace { | ||||
|  | @ -255,13 +255,13 @@ bool NotificationManager::PopNotification::push_background_color() | |||
| 		push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 		return true; | ||||
| 	} | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotification) { | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotificationLevel) { | ||||
| 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); | ||||
| 		backcolor.x += 0.3f; | ||||
| 		push_style_color(ImGuiCol_WindowBg, backcolor, m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 		return true; | ||||
| 	} | ||||
| 	if (m_data.level == NotificationLevel::WarningNotification) { | ||||
| 	if (m_data.level == NotificationLevel::WarningNotificationLevel) { | ||||
| 		ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg); | ||||
| 		backcolor.x += 0.3f; | ||||
| 		backcolor.y += 0.15f; | ||||
|  | @ -276,9 +276,9 @@ void NotificationManager::PopNotification::count_spaces() | |||
| 	m_line_height = ImGui::CalcTextSize("A").y; | ||||
| 
 | ||||
| 	m_left_indentation = m_line_height; | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { | ||||
| 		std::string text; | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		float picture_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
| 		m_left_indentation = picture_width + m_line_height / 2; | ||||
| 	} | ||||
|  | @ -505,9 +505,9 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img | |||
| 
 | ||||
| void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui) | ||||
| { | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { | ||||
| 		std::string text; | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		ImGui::SetCursorPosX(m_line_height / 3); | ||||
| 		ImGui::SetCursorPosY(m_window_height / 2 - m_line_height); | ||||
| 		imgui.text(text.c_str()); | ||||
|  | @ -583,19 +583,23 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 | |||
| 
 | ||||
| 	int64_t now = GLCanvas3D::timestamp_now(); | ||||
| 
 | ||||
| 	// reset fade opacity for non-closing notifications or hover during fading
 | ||||
| 	if (m_state != EState::FadingOut && m_state != EState::ClosePending && m_state != EState::Finished) { | ||||
| 		m_current_fade_opacity = 1.0f; | ||||
| 	} | ||||
| 
 | ||||
| 	// reset timers - hovered state is set in render 
 | ||||
| 	if (m_state == EState::Hovered) {  | ||||
| 		m_current_fade_opacity = 1.0f; | ||||
| 		m_state = EState::Unknown; | ||||
| 		init(); | ||||
| 	// Timers when not fading
 | ||||
| 	} else if (m_state != EState::NotFading && m_state != EState::FadingOut && m_data.duration != 0 && !paused) { | ||||
| 	} else if (m_state != EState::NotFading && m_state != EState::FadingOut && get_duration() != 0 && !paused) { | ||||
| 		int64_t up_time = now - m_notification_start; | ||||
| 		if (up_time >= m_data.duration * 1000) { | ||||
| 		if (up_time >= get_duration() * 1000) { | ||||
| 			m_state					= EState::FadingOut; | ||||
| 			m_fading_start			= now; | ||||
| 		} else { | ||||
| 			m_next_render = m_data.duration * 1000 - up_time; | ||||
| 			m_next_render = get_duration() * 1000 - up_time; | ||||
| 		}	 | ||||
| 	} | ||||
| 	// Timers when fading
 | ||||
|  | @ -626,53 +630,6 @@ bool NotificationManager::PopNotification::update_state(bool paused, const int64 | |||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool large) : | ||||
| 	  NotificationManager::PopNotification(n, id_provider, evt_handler) | ||||
| { | ||||
| 	set_large(large); | ||||
| } | ||||
| void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	if (!m_is_large) | ||||
| 		PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	else { | ||||
|         ImVec2 win_size(win_size_x, win_size_y); | ||||
| 		ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); | ||||
| 		float x_offset = m_left_indentation; | ||||
| 		std::string fulltext = m_text1 + m_hypertext + m_text2; | ||||
| 		ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); | ||||
| 		float cursor_y = win_size.y / 2 - text_size.y / 2; | ||||
| 		if (m_has_print_info) { | ||||
| 			x_offset = 20; | ||||
| 			cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(cursor_y); | ||||
| 			imgui.text(m_print_info.c_str()); | ||||
| 			cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; | ||||
| 		} | ||||
| 		ImGui::SetCursorPosX(x_offset); | ||||
| 		ImGui::SetCursorPosY(cursor_y); | ||||
| 		imgui.text(m_text1.c_str()); | ||||
| 
 | ||||
| 		render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::SlicingCompleteLargeNotification::set_print_info(const std::string &info) | ||||
| { | ||||
| 	m_print_info = info; | ||||
| 	m_has_print_info = true; | ||||
| 	if (m_is_large) | ||||
| 		m_lines_count = 2; | ||||
| } | ||||
| void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l) | ||||
| { | ||||
| 	m_is_large = l; | ||||
| 	//FIXME this information should not be lost (change m_data?)
 | ||||
| //	m_counting_down = !l;
 | ||||
| 	m_hypertext = l ? _u8L("Export G-Code.") : std::string(); | ||||
| 	m_state = l ? EState::Shown : EState::Hidden; | ||||
| 	init(); | ||||
| } | ||||
| //---------------ExportFinishedNotification-----------
 | ||||
| void NotificationManager::ExportFinishedNotification::count_spaces() | ||||
| { | ||||
|  | @ -680,9 +637,9 @@ void NotificationManager::ExportFinishedNotification::count_spaces() | |||
| 	m_line_height = ImGui::CalcTextSize("A").y; | ||||
| 
 | ||||
| 	m_left_indentation = m_line_height; | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) { | ||||
| 	if (m_data.level == NotificationLevel::ErrorNotificationLevel || m_data.level == NotificationLevel::WarningNotificationLevel) { | ||||
| 		std::string text; | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		float picture_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
| 		m_left_indentation = picture_width + m_line_height / 2; | ||||
| 	} | ||||
|  | @ -793,6 +750,9 @@ void NotificationManager::ProgressBarNotification::init() | |||
| { | ||||
| 	PopNotification::init(); | ||||
| 	//m_lines_count++;
 | ||||
| 	if (m_endlines.empty()) { | ||||
| 		m_endlines.push_back(0); | ||||
| 	} | ||||
| 	if(m_lines_count >= 2) { | ||||
| 		m_lines_count = 3; | ||||
| 		m_multiline = true; | ||||
|  | @ -897,8 +857,17 @@ void NotificationManager::ProgressBarNotification::render_bar(ImGuiWrapper& imgu | |||
| 	ImVec2 lineEnd				= ImVec2(win_pos_x - m_window_width_offset, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); | ||||
| 	ImVec2 lineStart			= ImVec2(win_pos_x - win_size_x + m_left_indentation, win_pos_y + win_size_y / 2 + (m_multiline ? m_line_height / 2 : 0)); | ||||
| 	ImVec2 midPoint				= ImVec2(lineStart.x + (lineEnd.x - lineStart.x) * m_percentage, lineStart.y); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (1.0f * 255.f)), m_line_height * 0.2f); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(gray_color.x * 255), (int)(gray_color.y * 255), (int)(gray_color.z * 255), (m_current_fade_opacity * 255.f)), m_line_height * 0.2f); | ||||
| 	ImGui::GetWindowDrawList()->AddLine(lineStart, midPoint, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (m_current_fade_opacity * 255.f)), m_line_height * 0.2f); | ||||
| 	if (m_render_percentage) { | ||||
| 		std::string text; | ||||
| 		std::stringstream stream; | ||||
| 		stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%"; | ||||
| 		text = stream.str(); | ||||
| 		ImGui::SetCursorPosX(m_left_indentation); | ||||
| 		ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); | ||||
| 		imgui.text(text.c_str()); | ||||
| 	} | ||||
| } | ||||
| //------PrintHostUploadNotification----------------
 | ||||
| void NotificationManager::PrintHostUploadNotification::init() | ||||
|  | @ -915,7 +884,7 @@ void NotificationManager::PrintHostUploadNotification::count_spaces() | |||
| 	m_left_indentation = m_line_height; | ||||
| 	if (m_uj_state == UploadJobState::PB_ERROR) { | ||||
| 		std::string text; | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		text = (m_data.level == NotificationLevel::ErrorNotificationLevel ? ImGui::ErrorMarker : ImGui::WarningMarker); | ||||
| 		float picture_width = ImGui::CalcTextSize(text.c_str()).x; | ||||
| 		m_left_indentation = picture_width + m_line_height / 2; | ||||
| 	} | ||||
|  | @ -1102,15 +1071,382 @@ void NotificationManager::UpdatedItemsInfoNotification::render_left_sign(ImGuiWr | |||
| 	imgui.text(text.c_str()); | ||||
| } | ||||
| 
 | ||||
| //------SlicingProgressNotificastion
 | ||||
| void NotificationManager::SlicingProgressNotification::init() | ||||
| { | ||||
| 	if (m_sp_state == SlicingProgressState::SP_PROGRESS) { | ||||
| 		ProgressBarNotification::init(); | ||||
| 		//if (m_state == EState::NotFading && m_percentage >= 1.0f)
 | ||||
| 		//	m_state = EState::Shown;
 | ||||
| 	} | ||||
| 	else { | ||||
| 		PopNotification::init(); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::set_progress_state(float percent) | ||||
| { | ||||
| 	if (percent < 0.f) | ||||
| 		set_progress_state(SlicingProgressState::SP_CANCELLED); | ||||
| 	else if (percent >= 1.f) | ||||
| 		set_progress_state(SlicingProgressState::SP_COMPLETED); | ||||
| 	else  | ||||
| 		set_progress_state(SlicingProgressState::SP_PROGRESS, percent); | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::set_progress_state(NotificationManager::SlicingProgressNotification::SlicingProgressState state, float percent/* = 0.f*/) | ||||
| { | ||||
| 	switch (state) | ||||
| 	{ | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING: | ||||
| 		m_state = EState::Hidden; | ||||
| 		set_percentage(-1); | ||||
| 		m_has_print_info = false; | ||||
| 		set_export_possible(false); | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_PROGRESS: | ||||
| 		set_percentage(percent); | ||||
| 		m_has_cancel_button = true; | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_CANCELLED: | ||||
| 		set_percentage(-1); | ||||
| 		m_has_cancel_button = false; | ||||
| 		m_has_print_info = false; | ||||
| 		set_export_possible(false); | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: | ||||
| 		set_percentage(1); | ||||
| 		m_has_cancel_button = false; | ||||
| 		m_has_print_info = false; | ||||
| 		// m_export_possible is important only for PROGRESS state, thus we can reset it here
 | ||||
| 		set_export_possible(false); | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| 	m_sp_state = state; | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::set_status_text(const std::string& text) | ||||
| { | ||||
| 	switch (m_sp_state) | ||||
| 	{ | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING: | ||||
| 		m_state = EState::Hidden; | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_PROGRESS: | ||||
| 	{ | ||||
| 		NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, text + ".",  m_is_fff ? _u8L("Export G-Code.") : _u8L("Export.") }; | ||||
| 		update(data); | ||||
| 		m_state = EState::NotFading; | ||||
| 	} | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_CANCELLED:  | ||||
| 	{ | ||||
| 		NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0, text }; | ||||
| 		update(data); | ||||
| 		m_state = EState::Shown; | ||||
| 	} | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::SlicingProgressNotification::SlicingProgressState::SP_COMPLETED: | ||||
| 	{ | ||||
| 		NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0,  _u8L("Slicing finished."), m_is_fff ? _u8L("Export G-Code.") : _u8L("Export.") }; | ||||
| 		update(data); | ||||
| 		m_state = EState::Shown; | ||||
| 	} | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::set_print_info(const std::string& info) | ||||
| { | ||||
| 	if (m_sp_state != SlicingProgressState::SP_COMPLETED) { | ||||
| 		set_progress_state (SlicingProgressState::SP_COMPLETED); | ||||
| 	} else { | ||||
| 		m_has_print_info = true; | ||||
| 		m_print_info = info; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::set_sidebar_collapsed(bool collapsed)  | ||||
| { | ||||
| 	m_sidebar_collapsed = collapsed; | ||||
| 	if (m_sp_state == SlicingProgressState::SP_COMPLETED) | ||||
| 		m_state = EState::Shown; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::SlicingProgressNotification::on_cancel_button() | ||||
| { | ||||
| 	if (m_cancel_callback){ | ||||
| 		m_cancel_callback(); | ||||
| 	} | ||||
| } | ||||
| int NotificationManager::SlicingProgressNotification::get_duration() | ||||
| { | ||||
| 	if (m_sp_state == SlicingProgressState::SP_CANCELLED) | ||||
| 		return 10; | ||||
| 	else if (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed) | ||||
| 		return 5; | ||||
| 	else | ||||
| 		return 0; | ||||
| } | ||||
| bool  NotificationManager::SlicingProgressNotification::update_state(bool paused, const int64_t delta) | ||||
| { | ||||
| 	bool ret = ProgressBarNotification::update_state(paused, delta); | ||||
| 	// sets Estate to hidden 
 | ||||
| 	if (get_state() == PopNotification::EState::ClosePending || get_state() == PopNotification::EState::Finished) | ||||
| 		set_progress_state(SlicingProgressState::SP_NO_SLICING); | ||||
| 	return ret; | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	if (m_sp_state == SlicingProgressState::SP_PROGRESS || (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed)) { | ||||
| 		ProgressBarNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 		/* // enable for hypertext during slicing (correct call of export_enabled needed)
 | ||||
| 		if (m_multiline) { | ||||
| 			// two lines text, one line bar
 | ||||
| 			ImGui::SetCursorPosX(m_left_indentation); | ||||
| 			ImGui::SetCursorPosY(m_line_height / 4); | ||||
| 			imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); | ||||
| 			ImGui::SetCursorPosX(m_left_indentation); | ||||
| 			ImGui::SetCursorPosY(m_line_height + m_line_height / 4); | ||||
| 			std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); | ||||
| 			imgui.text(line.c_str()); | ||||
| 			if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { | ||||
| 				ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); | ||||
| 				render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height + m_line_height / 4, m_hypertext); | ||||
| 			} | ||||
| 			if (m_has_cancel_button) | ||||
| 				render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 			render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 		} | ||||
| 		else { | ||||
| 			//one line text, one line bar
 | ||||
| 			ImGui::SetCursorPosX(m_left_indentation); | ||||
| 			ImGui::SetCursorPosY(m_line_height / 4); | ||||
| 			std::string line = m_text1.substr(0, m_endlines[0]); | ||||
| 			imgui.text(line.c_str()); | ||||
| 			if (m_sidebar_collapsed && m_sp_state == SlicingProgressState::SP_PROGRESS && m_export_possible) { | ||||
| 				ImVec2 text_size = ImGui::CalcTextSize(line.c_str()); | ||||
| 				render_hypertext(imgui, m_left_indentation + text_size.x + 4, m_line_height / 4, m_hypertext); | ||||
| 			} | ||||
| 			if (m_has_cancel_button) | ||||
| 				render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 			render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 		} | ||||
| 		*/ | ||||
| 	} else if (m_sp_state == SlicingProgressState::SP_COMPLETED) { | ||||
| 		// "Slicing Finished" on line 1 + hypertext, print info on line
 | ||||
| 		ImVec2 win_size(win_size_x, win_size_y); | ||||
| 		ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str()); | ||||
| 		float x_offset = m_left_indentation; | ||||
| 		std::string fulltext = m_text1 + m_hypertext + m_text2; | ||||
| 		ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str()); | ||||
| 		float cursor_y = win_size.y / 2 - text_size.y / 2; | ||||
| 		if (m_sidebar_collapsed && m_has_print_info) { | ||||
| 			x_offset = 20; | ||||
| 			cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2; | ||||
| 			ImGui::SetCursorPosX(x_offset); | ||||
| 			ImGui::SetCursorPosY(cursor_y); | ||||
| 			imgui.text(m_print_info.c_str()); | ||||
| 			cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2; | ||||
| 		} | ||||
| 		ImGui::SetCursorPosX(x_offset); | ||||
| 		ImGui::SetCursorPosY(cursor_y); | ||||
| 		imgui.text(m_text1.c_str()); | ||||
| 		if (m_sidebar_collapsed) | ||||
| 			render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext); | ||||
| 	} else { | ||||
| 		PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::render_bar(ImGuiWrapper& imgui,  const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	if (!(m_sp_state == SlicingProgressState::SP_PROGRESS || (m_sp_state == SlicingProgressState::SP_COMPLETED && !m_sidebar_collapsed))) { | ||||
| 		return; | ||||
| 	} | ||||
| 	//std::string text;
 | ||||
| 	ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	/*
 | ||||
| 	std::stringstream stream; | ||||
| 	stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%"; | ||||
| 	text = stream.str(); | ||||
| 	ImGui::SetCursorPosX(m_left_indentation); | ||||
| 	ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4)); | ||||
| 	imgui.text(text.c_str()); | ||||
| 	*/ | ||||
| } | ||||
| void NotificationManager::SlicingProgressNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	ImVec2 win_size(win_size_x, win_size_y); | ||||
| 	ImVec2 win_pos(win_pos_x, win_pos_y); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 
 | ||||
| 
 | ||||
| 	std::string button_text; | ||||
| 	button_text = ImGui::CancelButton; | ||||
| 
 | ||||
| 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), | ||||
| 		ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), | ||||
| 		true)) | ||||
| 	{ | ||||
| 		button_text = ImGui::CancelHoverButton; | ||||
| 	} | ||||
| 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); | ||||
| 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); | ||||
| 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); | ||||
| 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) | ||||
| 	{ | ||||
| 		on_cancel_button(); | ||||
| 	} | ||||
| 
 | ||||
| 	//invisible large button
 | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); | ||||
| 	ImGui::SetCursorPosY(0); | ||||
| 	if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) | ||||
| 	{ | ||||
| 		on_cancel_button(); | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| 	ImGui::PopStyleColor(); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::SlicingProgressNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	// Do not render close button while showing progress - cancel button is rendered instead
 | ||||
| 	if (m_sp_state != SlicingProgressState::SP_PROGRESS) { | ||||
| 		ProgressBarNotification::render_close_button(imgui, win_size_x,  win_size_y, win_pos_x, win_pos_y); | ||||
| 	} | ||||
| } | ||||
| //------ProgressIndicatorNotification-------
 | ||||
| void NotificationManager::ProgressIndicatorNotification::set_status_text(const char* text) | ||||
| { | ||||
| 	NotificationData data{ NotificationType::ProgressIndicator, NotificationLevel::ProgressBarNotificationLevel, 0, text }; | ||||
| 	update(data); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::ProgressIndicatorNotification::init() | ||||
| { | ||||
| 	// skip ProgressBarNotification::init (same code here)
 | ||||
| 	PopNotification::init(); | ||||
| 	if (m_endlines.empty()) { | ||||
| 		m_endlines.push_back(0); | ||||
| 	} | ||||
| 	if (m_lines_count >= 2) { | ||||
| 		m_lines_count = 3; | ||||
| 		m_multiline = true; | ||||
| 		while (m_endlines.size() < 3) | ||||
| 			m_endlines.push_back(m_endlines.back()); | ||||
| 	} | ||||
| 	else { | ||||
| 		m_lines_count = 2; | ||||
| 		m_endlines.push_back(m_endlines.back()); | ||||
| 	} | ||||
| 	switch (m_progress_state) | ||||
| 	{ | ||||
| 	case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_HIDDEN: | ||||
| 		m_state = EState::Hidden; | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_PROGRESS_REQUEST: | ||||
| 	case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_PROGRESS_UPDATED: | ||||
| 		m_state = EState::NotFading; | ||||
| 		break; | ||||
| 	case Slic3r::GUI::NotificationManager::ProgressIndicatorNotification::ProgressIndicatorState::PIS_COMPLETED: | ||||
| 		m_state = EState::Shown; | ||||
| 		break; | ||||
| 	default: | ||||
| 		break; | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::ProgressIndicatorNotification::set_percentage(float percent) | ||||
| { | ||||
| 	ProgressBarNotification::set_percentage(percent); | ||||
| 	if (percent >= 0.0f && percent < 1.0f) { | ||||
| 		m_state = EState::NotFading; | ||||
| 		m_has_cancel_button = true; | ||||
| 		m_progress_state = ProgressIndicatorState::PIS_PROGRESS_REQUEST; | ||||
| 	} else if (percent >= 1.0f) { | ||||
| 		m_state = EState::Shown; | ||||
| 		m_progress_state = ProgressIndicatorState::PIS_COMPLETED; | ||||
| 		m_has_cancel_button = false; | ||||
| 	} else { | ||||
| 		m_progress_state = ProgressIndicatorState::PIS_HIDDEN; | ||||
| 		m_state = EState::Hidden; | ||||
| 	} | ||||
| } | ||||
| bool NotificationManager::ProgressIndicatorNotification::update_state(bool paused, const int64_t delta) | ||||
| { | ||||
| 	if (m_progress_state == ProgressIndicatorState::PIS_PROGRESS_REQUEST) { | ||||
| 		// percentage was changed (and it called schedule_extra_frame), now update must know this needs render
 | ||||
| 		m_next_render = 0; | ||||
| 		m_progress_state = ProgressIndicatorState::PIS_PROGRESS_UPDATED; | ||||
| 		return true; | ||||
| 	} | ||||
| 	bool ret = ProgressBarNotification::update_state(paused, delta); | ||||
| 	if (get_state() == PopNotification::EState::ClosePending || get_state() == PopNotification::EState::Finished) | ||||
| 		// go to PIS_HIDDEN state
 | ||||
| 		set_percentage(-1.0f); | ||||
| 	return ret; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::ProgressIndicatorNotification::render_cancel_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	ImVec2 win_size(win_size_x, win_size_y); | ||||
| 	ImVec2 win_pos(win_pos_x, win_pos_y); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 	push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity); | ||||
| 	ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); | ||||
| 
 | ||||
| 
 | ||||
| 	std::string button_text; | ||||
| 	button_text = ImGui::CancelButton; | ||||
| 
 | ||||
| 	if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y), | ||||
| 		ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)), | ||||
| 		true)) | ||||
| 	{ | ||||
| 		button_text = ImGui::CancelHoverButton; | ||||
| 	} | ||||
| 	ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str()); | ||||
| 	ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f); | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f); | ||||
| 	ImGui::SetCursorPosY(win_size.y / 2 - button_size.y); | ||||
| 	if (imgui.button(button_text.c_str(), button_size.x, button_size.y)) | ||||
| 	{ | ||||
| 		on_cancel_button(); | ||||
| 	} | ||||
| 
 | ||||
| 	//invisible large button
 | ||||
| 	ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f); | ||||
| 	ImGui::SetCursorPosY(0); | ||||
| 	if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0))) | ||||
| 	{ | ||||
| 		on_cancel_button(); | ||||
| 	} | ||||
| 	ImGui::PopStyleColor(5); | ||||
| } | ||||
| void NotificationManager::ProgressIndicatorNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) | ||||
| { | ||||
| 	// Do not render close button while showing progress - cancel button is rendered instead
 | ||||
| 	if (m_percentage >= 1.0f) | ||||
| 	{ | ||||
| 		ProgressBarNotification::render_close_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y); | ||||
| 	} | ||||
| } | ||||
| //------NotificationManager--------
 | ||||
| NotificationManager::NotificationManager(wxEvtHandler* evt_handler) : | ||||
| 	m_evt_handler(evt_handler) | ||||
| { | ||||
| } | ||||
| NotificationManager::~NotificationManager() | ||||
| { | ||||
| 	HintDatabase::get_instance().uninit(); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::push_notification(const NotificationType type, int timestamp) | ||||
| { | ||||
| 	auto it = std::find_if(std::begin(basic_notifications), std::end(basic_notifications), | ||||
|  | @ -1121,7 +1457,7 @@ void NotificationManager::push_notification(const NotificationType type, int tim | |||
| } | ||||
| void NotificationManager::push_notification(const std::string& text, int timestamp) | ||||
| { | ||||
| 	push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, timestamp); | ||||
| 	push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotificationLevel, 10, text }, timestamp); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::push_notification(NotificationType type, | ||||
|  | @ -1133,11 +1469,11 @@ void NotificationManager::push_notification(NotificationType type, | |||
| { | ||||
| 	int duration = 0; | ||||
| 	switch (level) { | ||||
| 	case NotificationLevel::RegularNotification: 	 duration = 10; break; | ||||
| 	case NotificationLevel::ErrorNotification: 		 break; | ||||
| 	case NotificationLevel::WarningNotification:     break; | ||||
| 	case NotificationLevel::ImportantNotification: 	 break; | ||||
| 	case NotificationLevel::ProgressBarNotification: break; | ||||
| 	case NotificationLevel::RegularNotificationLevel: 	 duration = 10; break; | ||||
| 	case NotificationLevel::ErrorNotificationLevel: 		 break; | ||||
| 	case NotificationLevel::WarningNotificationLevel:		 break; | ||||
| 	case NotificationLevel::ImportantNotificationLevel: 	 break; | ||||
| 	case NotificationLevel::ProgressBarNotificationLevel:    break; | ||||
| 	default: | ||||
| 		assert(false); | ||||
| 		return; | ||||
|  | @ -1146,18 +1482,19 @@ void NotificationManager::push_notification(NotificationType type, | |||
| } | ||||
| void NotificationManager::push_validate_error_notification(const std::string& text) | ||||
| { | ||||
| 	push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, 0); | ||||
| 	push_notification_data({ NotificationType::ValidateError, NotificationLevel::ErrorNotificationLevel, 0,  _u8L("ERROR:") + "\n" + text }, 0); | ||||
| 	set_slicing_progress_hidden(); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::push_slicing_error_notification(const std::string& text) | ||||
| { | ||||
| 	set_all_slicing_errors_gray(false); | ||||
| 	push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, 0); | ||||
| 	close_notification_of_type(NotificationType::SlicingComplete); | ||||
| 	push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotificationLevel, 0,  _u8L("ERROR:") + "\n" + text }, 0); | ||||
| 	set_slicing_progress_hidden(); | ||||
| } | ||||
| void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, ObjectID oid, int warning_step) | ||||
| { | ||||
| 	NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }; | ||||
| 	NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotificationLevel, 0,  _u8L("WARNING:") + "\n" + text }; | ||||
| 
 | ||||
| 	auto notification = std::make_unique<NotificationManager::SlicingWarningNotification>(data, m_id_provider, m_evt_handler); | ||||
| 	notification->object_id = oid; | ||||
|  | @ -1168,7 +1505,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t | |||
| } | ||||
| void NotificationManager::push_plater_error_notification(const std::string& text) | ||||
| { | ||||
| 	push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0,  _u8L("ERROR:") + "\n" + text }, 0); | ||||
| 	push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotificationLevel, 0,  _u8L("ERROR:") + "\n" + text }, 0); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::close_plater_error_notification(const std::string& text) | ||||
|  | @ -1192,7 +1529,7 @@ void NotificationManager::push_plater_warning_notification(const std::string& te | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0,  _u8L("WARNING:") + "\n" + text }; | ||||
| 	NotificationData data{ NotificationType::PlaterWarning, NotificationLevel::WarningNotificationLevel, 0,  _u8L("WARNING:") + "\n" + text }; | ||||
| 
 | ||||
| 	auto notification = std::make_unique<NotificationManager::PlaterWarningNotification>(data, m_id_provider, m_evt_handler); | ||||
| 	push_notification_data(std::move(notification), 0); | ||||
|  | @ -1250,42 +1587,13 @@ void NotificationManager::close_slicing_error_notification(const std::string& te | |||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::push_slicing_complete_notification(int timestamp, bool large) | ||||
| void  NotificationManager::push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext/* = ""*/, std::function<bool(wxEvtHandler*)> callback/* = std::function<bool(wxEvtHandler*)>()*/) | ||||
| { | ||||
| 	std::string hypertext; | ||||
| 	int         time = 10; | ||||
|     if (has_slicing_error_notification()) | ||||
|         return; | ||||
|     if (large) { | ||||
| 		hypertext = _u8L("Export G-Code."); | ||||
| 		time = 0; | ||||
| 	} | ||||
|     NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time,  _u8L("Slicing finished."), hypertext, | ||||
|                            [](wxEvtHandler* evnthndlr){ | ||||
|                                if (evnthndlr != nullptr) | ||||
|                                    wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); | ||||
|                                return true; | ||||
|                            } | ||||
|     }; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::SlicingCompleteLargeNotification>(data, m_id_provider, m_evt_handler, large), timestamp); | ||||
| } | ||||
| void NotificationManager::set_slicing_complete_print_time(const std::string &info) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingComplete) { | ||||
| 			dynamic_cast<SlicingCompleteLargeNotification*>(notification.get())->set_print_info(info); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_slicing_complete_large(bool large) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification> ¬ification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingComplete) { | ||||
| 			dynamic_cast<SlicingCompleteLargeNotification*>(notification.get())->set_large(large); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 	NotificationData data{ NotificationType::ObjectWarning, NotificationLevel::WarningNotificationLevel, 0,  text, hypertext, callback }; | ||||
| 	auto notification = std::make_unique<NotificationManager::SlicingWarningNotification>(data, m_id_provider, m_evt_handler); | ||||
| 	notification->object_id = object_id; | ||||
| 	notification->warning_step = 0; | ||||
| 	push_notification_data(std::move(notification), 0); | ||||
| } | ||||
| void NotificationManager::close_notification_of_type(const NotificationType type) | ||||
| { | ||||
|  | @ -1304,10 +1612,19 @@ void NotificationManager::remove_slicing_warnings_of_released_objects(const std: | |||
| 				notification->close(); | ||||
| 		} | ||||
| } | ||||
| void NotificationManager::remove_object_warnings_of_released_objects(const std::vector<ObjectID>& living_oids) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) | ||||
| 		if (notification->get_type() == NotificationType::ObjectWarning) { | ||||
| 			if (!std::binary_search(living_oids.begin(), living_oids.end(), | ||||
| 				static_cast<SlicingWarningNotification*>(notification.get())->object_id)) | ||||
| 				notification->close(); | ||||
| 		} | ||||
| } | ||||
| void NotificationManager::push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable) | ||||
| { | ||||
| 	close_notification_of_type(NotificationType::ExportFinished); | ||||
| 	NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotification, on_removable ? 0 : 20,  _u8L("Exporting finished.") + "\n" + path }; | ||||
| 	NotificationData data{ NotificationType::ExportFinished, NotificationLevel::RegularNotificationLevel, on_removable ? 0 : 20,  _u8L("Exporting finished.") + "\n" + path }; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::ExportFinishedNotification>(data, m_id_provider, m_evt_handler, on_removable, path, dir_path), 0); | ||||
| } | ||||
| 
 | ||||
|  | @ -1321,7 +1638,7 @@ void  NotificationManager::push_upload_job_notification(int id, float filesize, | |||
| 		} | ||||
| 	} | ||||
| 	std::string text = PrintHostUploadNotification::get_upload_job_text(id, filename, host); | ||||
| 	NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotification, 10, text }; | ||||
| 	NotificationData data{ NotificationType::PrintHostUpload, NotificationLevel::ProgressBarNotificationLevel, 10, text }; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::PrintHostUploadNotification>(data, m_id_provider, m_evt_handler, 0, id, filesize), 0); | ||||
| } | ||||
| void NotificationManager::set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage) | ||||
|  | @ -1363,6 +1680,153 @@ void NotificationManager::upload_job_notification_show_error(int id, const std:: | |||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::init_slicing_progress_notification(std::function<void()> cancel_callback) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingProgress) { | ||||
| 			dynamic_cast<SlicingProgressNotification*>(notification.get())->set_cancel_callback(cancel_callback);	 | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	NotificationData data{ NotificationType::SlicingProgress, NotificationLevel::ProgressBarNotificationLevel, 0,  std::string(),std::string(), | ||||
| 						  [](wxEvtHandler* evnthndlr) { | ||||
| 							  if (evnthndlr != nullptr) | ||||
| 								  wxPostEvent(evnthndlr, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED)); | ||||
| 							  return true; | ||||
| 						  } | ||||
| 	}; | ||||
| 	push_notification_data(std::make_unique<NotificationManager::SlicingProgressNotification>(data, m_id_provider, m_evt_handler, cancel_callback), 0); | ||||
| } | ||||
| void NotificationManager::set_slicing_progress_percentage(const std::string& text, float percentage) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingProgress) { | ||||
| 			SlicingProgressNotification* spn = dynamic_cast<SlicingProgressNotification*>(notification.get()); | ||||
| 			spn->set_progress_state(percentage); | ||||
| 			spn->set_status_text(text); | ||||
| 			wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	// Slicing progress notification was not found - init it thru plater so correct cancel callback function is appended
 | ||||
| 	wxGetApp().plater()->init_notification_manager(); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::set_slicing_progress_hidden() | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingProgress) { | ||||
| 			SlicingProgressNotification* notif = dynamic_cast<SlicingProgressNotification*>(notification.get()); | ||||
| 			notif->set_progress_state(SlicingProgressNotification::SlicingProgressState::SP_NO_SLICING); | ||||
| 			wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	// Slicing progress notification was not found - init it thru plater so correct cancel callback function is appended
 | ||||
| 	wxGetApp().plater()->init_notification_manager(); | ||||
| } | ||||
| void NotificationManager::set_slicing_complete_print_time(const std::string& info, bool sidebar_colapsed) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingProgress) { | ||||
| 			dynamic_cast<SlicingProgressNotification*>(notification.get())->set_sidebar_collapsed(sidebar_colapsed); | ||||
| 			dynamic_cast<SlicingProgressNotification*>(notification.get())->set_print_info(info); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_sidebar_collapsed(bool collapsed) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingProgress) { | ||||
| 			dynamic_cast<SlicingProgressNotification*>(notification.get())->set_sidebar_collapsed(collapsed); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_fff(bool fff) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingProgress) { | ||||
| 			dynamic_cast<SlicingProgressNotification*>(notification.get())->set_fff(fff); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::set_slicing_progress_export_possible() | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::SlicingProgress) { | ||||
| 			dynamic_cast<SlicingProgressNotification*>(notification.get())->set_export_possible(true); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| void NotificationManager::init_progress_indicator() | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::ProgressIndicator) { | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	NotificationData data{ NotificationType::ProgressIndicator, NotificationLevel::ProgressBarNotificationLevel, 2}; | ||||
| 	auto notification = std::make_unique<NotificationManager::ProgressIndicatorNotification>(data, m_id_provider, m_evt_handler); | ||||
| 	push_notification_data(std::move(notification), 0); | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::progress_indicator_set_range(int range) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::ProgressIndicator) { | ||||
| 			dynamic_cast<ProgressIndicatorNotification*>(notification.get())->set_range(range); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	init_progress_indicator(); | ||||
| } | ||||
| void NotificationManager::progress_indicator_set_cancel_callback(CancelFn callback/* = CancelFn()*/) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::ProgressIndicator) { | ||||
| 			dynamic_cast<ProgressIndicatorNotification*>(notification.get())->set_cancel_callback(callback); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	init_progress_indicator(); | ||||
| } | ||||
| void NotificationManager::progress_indicator_set_progress(int pr) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::ProgressIndicator) { | ||||
| 			dynamic_cast<ProgressIndicatorNotification*>(notification.get())->set_progress(pr); | ||||
| 			// Ask for rendering - needs to be done on every progress. Calls to here doesnt trigger IDLE event or rendering. 
 | ||||
| 			wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(100); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	init_progress_indicator(); | ||||
| } | ||||
| void NotificationManager::progress_indicator_set_status_text(const char* text) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::ProgressIndicator) { | ||||
| 			dynamic_cast<ProgressIndicatorNotification*>(notification.get())->set_status_text(text); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	init_progress_indicator(); | ||||
| } | ||||
| int  NotificationManager::progress_indicator_get_range() const | ||||
| { | ||||
| 	for (const std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
| 		if (notification->get_type() == NotificationType::ProgressIndicator) { | ||||
| 			return dynamic_cast<ProgressIndicatorNotification*>(notification.get())->get_range(); | ||||
| 		} | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::push_hint_notification(bool open_next) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
|  | @ -1372,7 +1836,7 @@ void NotificationManager::push_hint_notification(bool open_next) | |||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 300, "" }; | ||||
| 	NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::HintNotificationLevel, 300, "" }; | ||||
| 	// from user - open now
 | ||||
| 	if (!open_next) { | ||||
| 		push_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), 0); | ||||
|  | @ -1380,8 +1844,8 @@ void NotificationManager::push_hint_notification(bool open_next) | |||
| 	// at startup - delay for half a second to let other notification pop up, than try every 30 seconds
 | ||||
| 	// show only if no notifications are shown
 | ||||
| 	} else {  | ||||
| 		auto condition = [this]() { | ||||
| 			return this->get_notification_count() == 0; | ||||
| 		auto condition = [&self = std::as_const(*this)]() { | ||||
| 			return self.get_notification_count() == 0; | ||||
| 		}; | ||||
| 		push_delayed_notification(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), condition, 500, 30000); | ||||
| 	} | ||||
|  | @ -1395,7 +1859,10 @@ bool NotificationManager::is_hint_notification_open() | |||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::deactivate_loaded_hints() | ||||
| { | ||||
| 	HintDatabase::get_instance().uninit(); | ||||
| } | ||||
| void NotificationManager::push_updated_item_info_notification(InfoItemType type) | ||||
| { | ||||
| 	for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) { | ||||
|  | @ -1405,7 +1872,7 @@ void NotificationManager::push_updated_item_info_notification(InfoItemType type) | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotification, 5, "" }; | ||||
| 	NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotificationLevel, 5, "" }; | ||||
| 	auto notification = std::make_unique<NotificationManager::UpdatedItemsInfoNotification>(data, m_id_provider, m_evt_handler, type); | ||||
| 	if (push_notification_data(std::move(notification), 0)) { | ||||
| 		(dynamic_cast<UpdatedItemsInfoNotification*>(m_pop_notifications.back().get()))->add_type(type); | ||||
|  | @ -1427,17 +1894,20 @@ bool NotificationManager::push_notification_data(std::unique_ptr<NotificationMan | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); | ||||
| 
 | ||||
| 	bool retval = false; | ||||
| 	if (this->activate_existing(notification.get())) { | ||||
| 		m_pop_notifications.back()->update(notification->get_data()); | ||||
| 		canvas.schedule_extra_frame(0); | ||||
| 		return false; | ||||
| 		if (m_initialized) { // ignore update action - it cant be initialized if canvas and imgui context is not ready
 | ||||
| 			m_pop_notifications.back()->update(notification->get_data()); | ||||
| 		} | ||||
| 	} else { | ||||
| 		m_pop_notifications.emplace_back(std::move(notification)); | ||||
| 		canvas.schedule_extra_frame(0); | ||||
| 		return true; | ||||
| 		retval = true; | ||||
| 	} | ||||
| 	if (!m_initialized) | ||||
| 		return retval; | ||||
| 	GLCanvas3D& canvas = *wxGetApp().plater()->get_current_canvas3D(); | ||||
| 	canvas.schedule_extra_frame(0); | ||||
| 	return retval; | ||||
| } | ||||
| 
 | ||||
| void NotificationManager::push_delayed_notification(std::unique_ptr<NotificationManager::PopNotification> notification, std::function<bool(void)> condition_callback, int64_t initial_delay, int64_t delay_interval) | ||||
|  | @ -1591,6 +2061,8 @@ void NotificationManager::set_in_preview(bool preview) | |||
|             notification->hide(preview); | ||||
|         if (notification->get_type() == NotificationType::SignDetected) | ||||
|             notification->hide(!preview); | ||||
| 		if (m_in_preview && notification->get_type() == NotificationType::DidYouKnowHint) | ||||
| 			notification->close(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include "GLCanvas3D.hpp" | ||||
| #include "Event.hpp" | ||||
| #include "I18N.hpp" | ||||
| #include "Jobs/ProgressIndicator.hpp" | ||||
| 
 | ||||
| #include <libslic3r/ObjectID.hpp> | ||||
| #include <libslic3r/Technologies.hpp> | ||||
|  | @ -27,6 +28,8 @@ wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationCli | |||
| using PresetUpdateAvailableClickedEvent = SimpleEvent; | ||||
| wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent); | ||||
| 
 | ||||
| using CancelFn = std::function<void()>; | ||||
| 
 | ||||
| class GLCanvas3D; | ||||
| class ImGuiWrapper; | ||||
| enum class InfoItemType; | ||||
|  | @ -34,9 +37,6 @@ enum class InfoItemType; | |||
| enum class NotificationType | ||||
| { | ||||
| 	CustomNotification, | ||||
| 	// Notification on end of slicing and G-code processing (the full G-code preview is available).
 | ||||
| 	// Contains a hyperlink to export the G-code to a removable media.
 | ||||
| 	SlicingComplete, | ||||
| //	SlicingNotPossible,
 | ||||
| 	// Notification on end of export to a removable media, with hyperling to eject the external media.
 | ||||
| 	// Obsolete by ExportFinished
 | ||||
|  | @ -71,10 +71,17 @@ enum class NotificationType | |||
| 	PlaterError, | ||||
| 	// Object fully outside the print volume, or extrusion outside the print volume. Slicing is not disabled.
 | ||||
| 	PlaterWarning, | ||||
| 	// Warning connected to single object id, appears at loading object, disapears at deletition.
 | ||||
| 	// Example: advice to simplify object with big amount of triangles.
 | ||||
| 	ObjectWarning, | ||||
| 	// Progress bar instead of text.
 | ||||
| 	ProgressBar, | ||||
| 	// Progress bar with info from Print Host Upload Queue dialog.
 | ||||
| 	PrintHostUpload, | ||||
| 	// Progress bar with cancel button, cannot be closed
 | ||||
| 	// On end of slicing and G-code processing (the full G-code preview is available),
 | ||||
| 	// contains a hyperlink to export the G-code to a removable media or hdd.
 | ||||
| 	SlicingProgress, | ||||
| 	// Notification, when Color Change G-code is empty and user try to add color change on DoubleSlider.
 | ||||
|     EmptyColorChangeCode, | ||||
|     // Notification that custom supports/seams were deleted after mesh repair.
 | ||||
|  | @ -97,8 +104,8 @@ enum class NotificationType | |||
| 	// Shows when  ObjectList::update_info_items finds information that should be stressed to the user
 | ||||
| 	// Might contain logo taken from gizmos
 | ||||
| 	UpdatedItemsInfo, | ||||
| 	// Give user advice to simplify object with big amount of triangles
 | ||||
| 	SimplifySuggestion | ||||
| 	// Progress bar notification with methods to replace ProgressIndicator class.
 | ||||
| 	ProgressIndicator | ||||
| }; | ||||
| 
 | ||||
| class NotificationManager | ||||
|  | @ -108,27 +115,31 @@ public: | |||
| 	{ | ||||
| 		// The notifications will be presented in the order of importance, thus these enum values
 | ||||
| 		// are sorted by the importance.
 | ||||
| 		// "Good to know" notification, usually but not always with a quick fade-out.
 | ||||
| 		RegularNotification = 1, | ||||
| 		// Important notification with progress bar, no fade-out, might appear again after closing. Position at the bottom.
 | ||||
| 		ProgressBarNotificationLevel = 1, | ||||
| 		// "Did you know" notification with special icon and buttons, Position close to bottom.
 | ||||
| 		HintNotificationLevel, | ||||
| 		// "Good to know" notification, usually but not always with a quick fade-out.		
 | ||||
| 		RegularNotificationLevel, | ||||
| 		// Information notification without a fade-out or with a longer fade-out.
 | ||||
| 		ImportantNotification, | ||||
| 		// Important notification with progress bar, no fade-out, might appear again after closing.
 | ||||
| 		ProgressBarNotification, | ||||
| 		ImportantNotificationLevel, | ||||
| 		// Warning, no fade-out.
 | ||||
| 		WarningNotification, | ||||
| 		// Error, no fade-out.
 | ||||
| 		ErrorNotification, | ||||
| 		WarningNotificationLevel, | ||||
| 		// Error, no fade-out. Top most position.
 | ||||
| 		ErrorNotificationLevel, | ||||
| 	}; | ||||
| 
 | ||||
| 	NotificationManager(wxEvtHandler* evt_handler); | ||||
| 	~NotificationManager(); | ||||
| 	~NotificationManager(){} | ||||
| 	 | ||||
| 	// init is called after canvas3d is created. Notifications added before init are not showed or updated
 | ||||
| 	void init() { m_initialized = true; } | ||||
| 	// Push a prefabricated notification from basic_notifications (see the table at the end of this file).
 | ||||
| 	void push_notification(const NotificationType type, int timestamp = 0); | ||||
| 	// Push a NotificationType::CustomNotification with NotificationLevel::RegularNotification and 10s fade out interval.
 | ||||
| 	// Push a NotificationType::CustomNotification with NotificationLevel::RegularNotificationLevel and 10s fade out interval.
 | ||||
| 	void push_notification(const std::string& text, int timestamp = 0); | ||||
| 	// Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotification.
 | ||||
| 	// ErrorNotification and ImportantNotification are never faded out.
 | ||||
| 	// Push a NotificationType::CustomNotification with provided notification level and 10s for RegularNotificationLevel.
 | ||||
| 	// ErrorNotificationLevel and ImportantNotificationLevel are never faded out.
 | ||||
|     void push_notification(NotificationType type, NotificationLevel level, const std::string& text, const std::string& hypertext = "", | ||||
|                            std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>(), int timestamp = 0); | ||||
| 	// Creates Validate Error notification with a custom text and no fade out.
 | ||||
|  | @ -155,25 +166,50 @@ public: | |||
| 	// Closes error or warning of the same text
 | ||||
| 	void close_plater_error_notification(const std::string& text); | ||||
| 	void close_plater_warning_notification(const std::string& text); | ||||
| 	// Creates special notification slicing complete.
 | ||||
| 	// If large = true (Plater side bar is closed), then printing time and export button is shown
 | ||||
| 	// at the notification and fade-out is disabled. Otherwise the fade out time is set to 10s.
 | ||||
| 	void push_slicing_complete_notification(int timestamp, bool large); | ||||
| 	// Add a print time estimate to an existing SlicingComplete notification.
 | ||||
| 	void set_slicing_complete_print_time(const std::string &info); | ||||
| 	// Object warning with ObjectID, closes when object is deleted. ID used is of object not print like in slicing warning.
 | ||||
| 	void push_object_warning_notification(const std::string& text, ObjectID object_id, const std::string& hypertext = "", | ||||
| 		std::function<bool(wxEvtHandler*)> callback = std::function<bool(wxEvtHandler*)>()); | ||||
| 	// Close object warnings, whose ObjectID is not in the list.
 | ||||
| 	// living_oids is expected to be sorted.
 | ||||
| 	void remove_object_warnings_of_released_objects(const std::vector<ObjectID>& living_oids); | ||||
| 	// Called when the side bar changes its visibility, as the "slicing complete" notification supplements
 | ||||
| 	// the "slicing info" normally shown at the side bar.
 | ||||
| 	void set_slicing_complete_large(bool large); | ||||
| 	void set_sidebar_collapsed(bool collapsed); | ||||
| 	// Set technology for correct text in SlicingProgress.
 | ||||
| 	void set_fff(bool b); | ||||
| 	void set_fdm(bool b) { set_fff(b); } | ||||
| 	void set_sla(bool b) { set_fff(!b); } | ||||
| 	// Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button
 | ||||
| 	void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable); | ||||
| 	// notification with progress bar
 | ||||
| 	// notifications with progress bar
 | ||||
| 	// print host upload
 | ||||
| 	void push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage = 0); | ||||
| 	void set_upload_job_notification_percentage(int id, const std::string& filename, const std::string& host, float percentage); | ||||
| 	void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host); | ||||
| 	void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host); | ||||
| 	// slicing progress
 | ||||
| 	void init_slicing_progress_notification(std::function<void()> cancel_callback); | ||||
| 	// percentage negative = canceled, <0-1) = progress, 1 = completed 
 | ||||
| 	void set_slicing_progress_percentage(const std::string& text, float percentage); | ||||
| 	// hides slicing progress notification imidietly
 | ||||
| 	void set_slicing_progress_hidden(); | ||||
| 	// Add a print time estimate to an existing SlicingProgress notification. Set said notification to SP_COMPLETED state.
 | ||||
| 	void set_slicing_complete_print_time(const std::string& info, bool sidebar_colapsed); | ||||
| 	void set_slicing_progress_export_possible(); | ||||
| 	// ProgressIndicator notification
 | ||||
| 	// init adds hidden instance of progress indi notif that should always live (goes to hidden instead of erasing)
 | ||||
| 	void init_progress_indicator(); | ||||
| 	// functions equal to ProgressIndicator class
 | ||||
| 	void progress_indicator_set_range(int range); | ||||
| 	void progress_indicator_set_cancel_callback(CancelFn callback = CancelFn()); | ||||
| 	void progress_indicator_set_progress(int pr); | ||||
| 	void progress_indicator_set_status_text(const char*); // utf8 char array
 | ||||
| 	int  progress_indicator_get_range() const; | ||||
| 	// Hint (did you know) notification
 | ||||
| 	void push_hint_notification(bool open_next); | ||||
| 	bool is_hint_notification_open(); | ||||
| 	// Forces Hints to reload its content when next hint should be showed
 | ||||
| 	void deactivate_loaded_hints(); | ||||
| 	void push_updated_item_info_notification(InfoItemType type); | ||||
| 	// Close old notification ExportFinished.
 | ||||
| 	void new_export_began(bool on_removable); | ||||
|  | @ -260,7 +296,7 @@ private: | |||
| 		virtual bool           compare_text(const std::string& text) const; | ||||
|         void                   hide(bool h) { if (is_finished()) return; m_state = h ? EState::Hidden : EState::Unknown; } | ||||
| 		// sets m_next_render with time of next mandatory rendering. Delta is time since last render.
 | ||||
| 		bool                   update_state(bool paused, const int64_t delta); | ||||
| 		virtual bool           update_state(bool paused, const int64_t delta); | ||||
| 		int64_t 		       next_render() const { return is_finished() ? 0 : m_next_render; } | ||||
| 		EState                 get_state()  const { return m_state; } | ||||
| 		bool				   is_hovered() const { return m_state == EState::Hovered; }  | ||||
|  | @ -296,6 +332,8 @@ private: | |||
| 		virtual void count_lines(); | ||||
| 		// returns true if PopStyleColor should be called later to pop this push
 | ||||
| 		virtual bool push_background_color(); | ||||
| 		// used this function instead of reading directly m_data.duration. Some notifications might need to return changing value.
 | ||||
| 		virtual int  get_duration() { return m_data.duration; } | ||||
| 
 | ||||
| 		const NotificationData m_data; | ||||
| 		// For reusing ImGUI windows.
 | ||||
|  | @ -352,29 +390,7 @@ private: | |||
| 		wxEvtHandler*    m_evt_handler; | ||||
| 	}; | ||||
| 
 | ||||
| 	class SlicingCompleteLargeNotification : public PopNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		SlicingCompleteLargeNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool largeds); | ||||
| 		void			set_large(bool l); | ||||
| 		bool			get_large() { return m_is_large; } | ||||
| 		void			set_print_info(const std::string &info); | ||||
| 		void			render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width) override | ||||
| 		{ | ||||
| 			// This notification is always hidden if !large (means side bar is collapsed)
 | ||||
| 			if (!get_large() && !is_finished())  | ||||
| 				m_state = EState::Hidden; | ||||
| 			PopNotification::render(canvas, initial_y, move_from_overlay, overlay_width); | ||||
| 		} | ||||
| 	protected: | ||||
| 		void render_text(ImGuiWrapper& imgui, | ||||
| 			                     const float win_size_x, const float win_size_y, | ||||
| 			                     const float win_pos_x, const float win_pos_y)  | ||||
| 			                     override; | ||||
| 		bool        m_is_large; | ||||
| 		bool        m_has_print_info { false }; | ||||
|         std::string m_print_info; | ||||
| 	}; | ||||
| 	 | ||||
| 
 | ||||
| 	class SlicingWarningNotification : public PopNotification | ||||
| 	{ | ||||
|  | @ -398,7 +414,7 @@ private: | |||
| 	{ | ||||
| 	public: | ||||
| 		 | ||||
| 		ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage) : PopNotification(n, id_provider, evt_handler) { } | ||||
| 		ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) { } | ||||
| 		virtual void set_percentage(float percent) { m_percentage = percent; } | ||||
| 	protected: | ||||
| 		virtual void init() override; | ||||
|  | @ -416,9 +432,10 @@ private: | |||
| 		{} | ||||
| 		void			render_minimize_button(ImGuiWrapper& imgui, | ||||
| 			const float win_pos_x, const float win_pos_y) override {} | ||||
| 		float				m_percentage; | ||||
| 		float				m_percentage {0.0f}; | ||||
| 		 | ||||
| 		bool				m_has_cancel_button {false}; | ||||
| 		bool                m_render_percentage {false}; | ||||
| 		// local time of last hover for showing tooltip
 | ||||
| 		 | ||||
| 	}; | ||||
|  | @ -436,7 +453,7 @@ private: | |||
| 			PB_COMPLETED | ||||
| 		}; | ||||
| 		PrintHostUploadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, float percentage, int job_id, float filesize) | ||||
| 			:ProgressBarNotification(n, id_provider, evt_handler, percentage) | ||||
| 			:ProgressBarNotification(n, id_provider, evt_handler) | ||||
| 			, m_job_id(job_id) | ||||
| 			, m_file_size(filesize) | ||||
| 		{ | ||||
|  | @ -465,7 +482,117 @@ private: | |||
| 		// Size of uploaded size to be displayed in MB
 | ||||
| 		float			    m_file_size; | ||||
| 		long				m_hover_time{ 0 }; | ||||
| 		UploadJobState	m_uj_state{ UploadJobState::PB_PROGRESS }; | ||||
| 		UploadJobState		m_uj_state{ UploadJobState::PB_PROGRESS }; | ||||
| 	}; | ||||
| 
 | ||||
| 	class SlicingProgressNotification : public ProgressBarNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		// Inner state of notification, Each state changes bahaviour of the notification
 | ||||
| 		enum class SlicingProgressState | ||||
| 		{ | ||||
| 			SP_NO_SLICING, // hidden
 | ||||
| 			SP_PROGRESS, // never fades outs, no close button, has cancel button
 | ||||
| 			SP_CANCELLED, // fades after 10 seconds, simple message
 | ||||
| 			SP_COMPLETED // Has export hyperlink and print info, fades after 20 sec if sidebar is shown, otherwise no fade out
 | ||||
| 		}; | ||||
| 		SlicingProgressNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, std::function<void()> callback) | ||||
| 		: ProgressBarNotification(n, id_provider, evt_handler) | ||||
| 		, m_cancel_callback(callback) | ||||
| 		{ | ||||
| 			set_progress_state(SlicingProgressState::SP_NO_SLICING); | ||||
| 			m_has_cancel_button = false; | ||||
| 			m_render_percentage = true; | ||||
| 		} | ||||
| 		// sets text of notification - call after setting progress state
 | ||||
| 		void				set_status_text(const std::string& text); | ||||
| 		// sets cancel button callback
 | ||||
| 		void			    set_cancel_callback(std::function<void()> callback) { m_cancel_callback = callback; } | ||||
| 		bool                has_cancel_callback() const { return m_cancel_callback != nullptr; } | ||||
| 		// sets SlicingProgressState, negative percent means canceled
 | ||||
| 		void				set_progress_state(float percent); | ||||
| 		// sets SlicingProgressState, percent is used only at progress state.
 | ||||
| 		void				set_progress_state(SlicingProgressState state,float percent = 0.f); | ||||
| 		// sets additional string of print info and puts notification into Completed state.
 | ||||
| 		void			    set_print_info(const std::string& info); | ||||
| 		// sets fading if in Completed state.
 | ||||
| 		void                set_sidebar_collapsed(bool collapsed); | ||||
| 		// Calls inherited update_state and ensures Estate goes to hidden not closing.
 | ||||
| 		bool                update_state(bool paused, const int64_t delta) override; | ||||
| 		// Switch between technology to provide correct text.
 | ||||
| 		void				set_fff(bool b) { m_is_fff = b; } | ||||
| 		void				set_fdm(bool b) { m_is_fff = b; } | ||||
| 		void				set_sla(bool b) { m_is_fff = !b; } | ||||
| 		void                set_export_possible(bool b) { m_export_possible = b; } | ||||
| 	protected: | ||||
| 		void        init() override; | ||||
| 		void        count_lines() override  | ||||
| 		{ | ||||
| 			if (m_sp_state == SlicingProgressState::SP_PROGRESS) | ||||
| 				ProgressBarNotification::count_lines(); | ||||
| 			else | ||||
| 				PopNotification::count_lines(); | ||||
| 		} | ||||
| 		void	    render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) override; | ||||
| 		void		render_bar(ImGuiWrapper& imgui, | ||||
| 								const float win_size_x, const float win_size_y, | ||||
| 								const float win_pos_x, const float win_pos_y) override; | ||||
| 		void		render_cancel_button(ImGuiWrapper& imgui, | ||||
| 											const float win_size_x, const float win_size_y, | ||||
| 											const float win_pos_x, const float win_pos_y) override; | ||||
| 		void		render_close_button(ImGuiWrapper& imgui, | ||||
| 									const float win_size_x, const float win_size_y, | ||||
| 									const float win_pos_x, const float win_pos_y) override; | ||||
| 		void       on_cancel_button(); | ||||
| 		int		   get_duration() override; | ||||
| 		std::function<void()>	m_cancel_callback; | ||||
| 		SlicingProgressState	m_sp_state { SlicingProgressState::SP_PROGRESS }; | ||||
| 		bool				    m_has_print_info { false }; | ||||
| 		std::string             m_print_info; | ||||
| 		bool                    m_sidebar_collapsed { false }; | ||||
| 		bool					m_is_fff { true }; | ||||
| 		// if true, it is possible show export hyperlink in state SP_PROGRESS
 | ||||
| 		bool                    m_export_possible { false }; | ||||
| 	}; | ||||
| 
 | ||||
| 	class ProgressIndicatorNotification : public ProgressBarNotification | ||||
| 	{ | ||||
| 	public: | ||||
| 		enum class ProgressIndicatorState | ||||
| 		{ | ||||
| 			PIS_HIDDEN, // hidden
 | ||||
| 			PIS_PROGRESS_REQUEST, // progress was updated, request render on next update_state() call
 | ||||
| 			PIS_PROGRESS_UPDATED, // render was requested
 | ||||
| 			PIS_COMPLETED // both completed and canceled state. fades out into PIS_NO_SLICING
 | ||||
| 		}; | ||||
| 		ProgressIndicatorNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler)  | ||||
| 		: ProgressBarNotification(n, id_provider, evt_handler)  | ||||
| 		{ | ||||
| 			m_render_percentage = true; | ||||
| 		} | ||||
| 		// ProgressIndicator 
 | ||||
| 		void set_range(int range) { m_range = range; } | ||||
| 		void set_cancel_callback(CancelFn callback) { m_cancel_callback = callback; } | ||||
| 		void set_progress(int pr) { set_percentage((float)pr / (float)m_range); } | ||||
| 		void set_status_text(const char*); // utf8 char array
 | ||||
| 		int  get_range() const { return m_range; } | ||||
| 		// ProgressBarNotification
 | ||||
| 		void init() override; | ||||
| 		void set_percentage(float percent) override; | ||||
| 		bool update_state(bool paused, const int64_t delta) override; | ||||
| 		// Own
 | ||||
| 	protected: | ||||
| 		int						m_range { 100 }; | ||||
| 		CancelFn				m_cancel_callback { nullptr }; | ||||
| 		ProgressIndicatorState  m_progress_state { ProgressIndicatorState::PIS_HIDDEN }; | ||||
| 
 | ||||
| 		void		render_close_button(ImGuiWrapper& imgui, | ||||
| 			                            const float win_size_x, const float win_size_y, | ||||
| 			                            const float win_pos_x, const float win_pos_y) override; | ||||
| 		void		render_cancel_button(ImGuiWrapper& imgui, | ||||
| 									     const float win_size_x, const float win_size_y, | ||||
| 									     const float win_pos_x, const float win_pos_y) override; | ||||
| 		void        on_cancel_button() { if (m_cancel_callback) m_cancel_callback(); } | ||||
| 	}; | ||||
| 
 | ||||
| 	class ExportFinishedNotification : public PopNotification | ||||
|  | @ -492,7 +619,7 @@ private: | |||
| 		void render_close_button(ImGuiWrapper& imgui, | ||||
| 								 const float win_size_x, const float win_size_y, | ||||
| 								 const float win_pos_x, const float win_pos_y) override; | ||||
| 		void         render_eject_button(ImGuiWrapper& imgui, | ||||
| 		void render_eject_button(ImGuiWrapper& imgui, | ||||
| 			                             const float win_size_x, const float win_size_y, | ||||
| 			                             const float win_pos_x, const float win_pos_y); | ||||
| 		void render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y) override | ||||
|  | @ -555,13 +682,15 @@ private: | |||
| 	// If there is some error notification active, then the "Export G-code" notification after the slicing is finished is suppressed.
 | ||||
|     bool has_slicing_error_notification(); | ||||
|      | ||||
| 	// set by init(), until false notifications are only added not updated and frame is not requested after push
 | ||||
| 	bool m_initialized{ false }; | ||||
| 	// Target for wxWidgets events sent by clicking on the hyperlink available at some notifications.
 | ||||
| 	wxEvtHandler*                m_evt_handler; | ||||
| 	// Cache of IDs to identify and reuse ImGUI windows.
 | ||||
| 	NotificationIDProvider 		 m_id_provider; | ||||
| 	std::deque<std::unique_ptr<PopNotification>> m_pop_notifications; | ||||
| 	// delayed waiting notifications, first is remaining time
 | ||||
| 	std::deque<DelayedNotification> m_waiting_notifications; | ||||
| 	std::vector<DelayedNotification> m_waiting_notifications; | ||||
| 	//timestamps used for slicing finished - notification could be gone so it needs to be stored here
 | ||||
| 	std::unordered_set<int>      m_used_timestamps; | ||||
| 	// True if G-code preview is active. False if the Plater is active.
 | ||||
|  | @ -576,7 +705,7 @@ private: | |||
| 		NotificationType::PlaterWarning,  | ||||
| 		NotificationType::ProgressBar,  | ||||
| 		NotificationType::PrintHostUpload,  | ||||
|         NotificationType::SimplifySuggestion | ||||
|         NotificationType::ObjectWarning | ||||
| 	}; | ||||
| 	//prepared (basic) notifications
 | ||||
| 	static const NotificationData basic_notifications[]; | ||||
|  |  | |||
|  | @ -1208,6 +1208,8 @@ void Sidebar::update_sliced_info_sizer() | |||
|             wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time)); | ||||
|             p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":"); | ||||
| 
 | ||||
|             p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + boost::nowide::narrow(t_est), p->plater->is_sidebar_collapsed()); | ||||
| 
 | ||||
|             // Hide non-SLA sliced info parameters
 | ||||
|             p->sliced_info->SetTextAndShow(siFilament_m, "N/A"); | ||||
|             p->sliced_info->SetTextAndShow(siFilament_mm3, "N/A"); | ||||
|  | @ -1296,10 +1298,7 @@ void Sidebar::update_sliced_info_sizer() | |||
|                     new_label += format_wxstr("\n   - %1%", _L("normal mode")); | ||||
|                     info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time)); | ||||
| 
 | ||||
|                     // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate
 | ||||
|                     //if (p->plater->is_sidebar_collapsed())
 | ||||
|                     p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); | ||||
|                     p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); | ||||
|                     p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + ps.estimated_normal_print_time, p->plater->is_sidebar_collapsed()); | ||||
| 
 | ||||
|                 } | ||||
|                 if (ps.estimated_silent_print_time != "N/A") { | ||||
|  | @ -1502,7 +1501,7 @@ struct Plater::priv | |||
|     GLToolbar view_toolbar; | ||||
|     GLToolbar collapse_toolbar; | ||||
|     Preview *preview; | ||||
|     NotificationManager* notification_manager { nullptr }; | ||||
|     std::shared_ptr<NotificationManager> notification_manager; | ||||
| 
 | ||||
|     ProjectDirtyStateManager dirty_state; | ||||
| 
 | ||||
|  | @ -1523,10 +1522,10 @@ struct Plater::priv | |||
|     public: | ||||
|         Jobs(priv *_m) : m(_m) | ||||
|         { | ||||
|             m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->statusbar(), m->q)); | ||||
|             m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m->statusbar(), m->q)); | ||||
|             m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->statusbar(), m->q)); | ||||
|             m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->statusbar(), m->q)); | ||||
|             m_arrange_id = add_job(std::make_unique<ArrangeJob>(m->notification_manager, m->q)); | ||||
|             m_fill_bed_id = add_job(std::make_unique<FillBedJob>(m->notification_manager, m->q)); | ||||
|             m_rotoptimize_id = add_job(std::make_unique<RotoptimizeJob>(m->notification_manager, m->q)); | ||||
|             m_sla_import_id = add_job(std::make_unique<SLAImportJob>(m->notification_manager, m->q)); | ||||
|         } | ||||
|          | ||||
|         void arrange() | ||||
|  | @ -1637,7 +1636,7 @@ struct Plater::priv | |||
|     void apply_free_camera_correction(bool apply = true); | ||||
|     void update_ui_from_settings(); | ||||
|     void update_main_toolbar_tooltips(); | ||||
|     std::shared_ptr<ProgressStatusBar> statusbar(); | ||||
| //   std::shared_ptr<ProgressStatusBar> statusbar();
 | ||||
|     std::string get_config(const std::string &key) const; | ||||
|     BoundingBoxf bed_shape_bb() const; | ||||
|     BoundingBox scaled_bed_shape_bb() const; | ||||
|  | @ -1739,6 +1738,7 @@ struct Plater::priv | |||
| 	void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid); | ||||
|     // Update notification manager with the current state of warnings produced by the background process (slicing).
 | ||||
| 	void actualize_slicing_warnings(const PrintBase &print); | ||||
|     void actualize_object_warnings(const PrintBase& print); | ||||
| 	// Displays dialog window with list of warnings. 
 | ||||
| 	// Returns true if user clicks OK.
 | ||||
| 	// Returns true if current_warnings vector is empty without showning the dialog
 | ||||
|  | @ -1790,6 +1790,8 @@ struct Plater::priv | |||
|     // extension should contain the leading dot, i.e.: ".3mf"
 | ||||
|     wxString get_project_filename(const wxString& extension = wxEmptyString) const; | ||||
|     void set_project_filename(const wxString& filename); | ||||
|     // Call after plater and Canvas#D is initialized
 | ||||
|     void init_notification_manager(); | ||||
| 
 | ||||
|     // Caching last value of show_action_buttons parameter for show_action_buttons(), so that a callback which does not know this state will not override it.
 | ||||
|     mutable bool    			ready_to_slice = { false }; | ||||
|  | @ -1799,6 +1801,7 @@ struct Plater::priv | |||
|     std::string                 last_output_dir_path; | ||||
|     bool                        inside_snapshot_capture() { return m_prevent_snapshots != 0; } | ||||
| 	bool                        process_completed_with_error { false }; | ||||
|     | ||||
| private: | ||||
|     bool layers_height_allowed() const; | ||||
| 
 | ||||
|  | @ -1847,6 +1850,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|         "support_material_contact_distance", "support_material_bottom_contact_distance", "raft_layers" | ||||
|         })) | ||||
|     , sidebar(new Sidebar(q)) | ||||
|     , notification_manager(std::make_shared<NotificationManager>(q)) | ||||
|     , m_ui_jobs(this) | ||||
|     , delayed_scene_refresh(false) | ||||
|     , view_toolbar(GLToolbar::Radio, "View") | ||||
|  | @ -2014,7 +2018,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|         }); | ||||
| #endif /* _WIN32 */ | ||||
| 
 | ||||
| 	notification_manager = new NotificationManager(this->q); | ||||
| 	//notification_manager = new NotificationManager(this->q);
 | ||||
| 
 | ||||
|     if (wxGetApp().is_editor()) { | ||||
|         this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); | ||||
|         this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); | ||||
|  | @ -2024,12 +2029,12 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
| 			    this->show_action_buttons(this->ready_to_slice); | ||||
|                 notification_manager->close_notification_of_type(NotificationType::ExportFinished); | ||||
|                 notification_manager->push_notification(NotificationType::CustomNotification, | ||||
|                                                         NotificationManager::NotificationLevel::RegularNotification, | ||||
|                                                         NotificationManager::NotificationLevel::RegularNotificationLevel, | ||||
|                                                         format(_L("Successfully unmounted. The device %s(%s) can now be safely removed from the computer."), evt.data.first.name, evt.data.first.path) | ||||
|                     ); | ||||
|             } else { | ||||
|                 notification_manager->push_notification(NotificationType::CustomNotification, | ||||
|                                                         NotificationManager::NotificationLevel::ErrorNotification, | ||||
|                                                         NotificationManager::NotificationLevel::ErrorNotificationLevel, | ||||
|                                                         format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path) | ||||
|                     ); | ||||
|             } | ||||
|  | @ -2076,8 +2081,7 @@ Plater::priv::~priv() | |||
| { | ||||
|     if (config != nullptr) | ||||
|         delete config; | ||||
|     if (notification_manager != nullptr) | ||||
|         delete notification_manager; | ||||
|     notification_manager->deactivate_loaded_hints(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::update(unsigned int flags) | ||||
|  | @ -2152,6 +2156,8 @@ void Plater::priv::collapse_sidebar(bool collapse) | |||
|     new_tooltip += " [Shift+Tab]"; | ||||
|     int id = collapse_toolbar.get_item_id("collapse_sidebar"); | ||||
|     collapse_toolbar.set_tooltip(id, new_tooltip); | ||||
| 
 | ||||
|     notification_manager->set_sidebar_collapsed(collapse); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -2179,10 +2185,11 @@ void Plater::priv::update_main_toolbar_tooltips() | |||
|     view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar(); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar() | ||||
| { | ||||
|     return main_frame->m_statusbar; | ||||
| } | ||||
| //std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar()
 | ||||
| //{
 | ||||
| //      return nullptr;
 | ||||
| //    return main_frame->m_statusbar;
 | ||||
| //}
 | ||||
| 
 | ||||
| std::string Plater::priv::get_config(const std::string &key) const | ||||
| { | ||||
|  | @ -2327,7 +2334,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
|                                 for (std::string& name : names) | ||||
|                                     notif_text += "\n - " + name; | ||||
|                                 notification_manager->push_notification(NotificationType::CustomNotification, | ||||
|                                     NotificationManager::NotificationLevel::RegularNotification, notif_text); | ||||
|                                     NotificationManager::NotificationLevel::RegularNotificationLevel, notif_text); | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|  | @ -2469,7 +2476,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_ | |||
|     if (load_model) { | ||||
|         wxGetApp().app_config->update_skein_dir(input_files[input_files.size() - 1].parent_path().make_preferred().string()); | ||||
|         // XXX: Plater.pm had @loaded_files, but didn't seem to fill them with the filenames...
 | ||||
|         statusbar()->set_status_text(_L("Loaded")); | ||||
| //        statusbar()->set_status_text(_L("Loaded"));
 | ||||
|     } | ||||
| 
 | ||||
|     // automatic selection of added objects
 | ||||
|  | @ -2882,7 +2889,7 @@ void Plater::priv::split_object() | |||
|         // If we splited object which is contain some parts/modifiers then all non-solid parts (modifiers) were deleted
 | ||||
|         if (current_model_object->volumes.size() > 1 && current_model_object->volumes.size() != new_objects.size()) | ||||
|             notification_manager->push_notification(NotificationType::CustomNotification, | ||||
|                 NotificationManager::NotificationLevel::RegularNotification, | ||||
|                 NotificationManager::NotificationLevel::RegularNotificationLevel, | ||||
|                 _u8L("All non-solid parts (modifiers) were deleted")); | ||||
| 
 | ||||
|         Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); | ||||
|  | @ -2958,7 +2965,7 @@ void Plater::priv::process_validation_warning(const std::string& warning) const | |||
| 
 | ||||
|         notification_manager->push_notification( | ||||
|             NotificationType::ValidateWarning, | ||||
|             NotificationManager::NotificationLevel::WarningNotification, | ||||
|             NotificationManager::NotificationLevel::WarningNotificationLevel, | ||||
|             _u8L("WARNING:") + "\n" + text, hypertext, action_fn | ||||
|         ); | ||||
|     } | ||||
|  | @ -3004,6 +3011,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool | |||
|         // In SLA mode, we need to reload the 3D scene every time to show the support structures.
 | ||||
|         if (printer_technology == ptSLA || (printer_technology == ptFFF && config->opt_bool("wipe_tower"))) | ||||
|             return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; | ||||
| 
 | ||||
|         notification_manager->set_slicing_progress_hidden(); | ||||
|     } | ||||
| 
 | ||||
|     if ((invalidated != Print::APPLY_STATUS_UNCHANGED || force_validation) && ! background_process.empty()) { | ||||
|  | @ -3051,6 +3060,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool | |||
| 	//actualizate warnings
 | ||||
| 	if (invalidated != Print::APPLY_STATUS_UNCHANGED) { | ||||
| 		actualize_slicing_warnings(*this->background_process.current_print()); | ||||
|         actualize_object_warnings(*this->background_process.current_print()); | ||||
| 		show_warning_dialog = false; | ||||
| 		process_completed_with_error = false; | ||||
| 	} | ||||
|  | @ -3075,9 +3085,9 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool | |||
|     else | ||||
|     { | ||||
|         // Background data is valid.
 | ||||
|         if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 || | ||||
|             (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 ) | ||||
|             this->statusbar()->set_status_text(_L("Ready to slice")); | ||||
| //        if ((return_state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ||
 | ||||
| //            (return_state & UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE) != 0 )
 | ||||
| //            this->statusbar()->set_status_text(_L("Ready to slice"));
 | ||||
| 
 | ||||
|         sidebar->set_btn_label(ActionButtonType::abExport, _(label_btn_export)); | ||||
|         sidebar->set_btn_label(ActionButtonType::abSendGCode, _(label_btn_send)); | ||||
|  | @ -3114,10 +3124,10 @@ bool Plater::priv::restart_background_process(unsigned int state) | |||
|            (state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) { | ||||
|         // The print is valid and it can be started.
 | ||||
|         if (this->background_process.start()) { | ||||
|             this->statusbar()->set_cancel_callback([this]() { | ||||
|                 this->statusbar()->set_status_text(_L("Cancelling")); | ||||
|                 this->background_process.stop(); | ||||
|             }); | ||||
| //            this->statusbar()->set_cancel_callback([this]() {
 | ||||
| //                this->statusbar()->set_status_text(_L("Cancelling"));
 | ||||
| //                this->background_process.stop();
 | ||||
| //            });
 | ||||
| 			if (!show_warning_dialog) | ||||
| 				on_slicing_began(); | ||||
|             return true; | ||||
|  | @ -3715,10 +3725,7 @@ void Plater::priv::create_simplify_notification(const std::vector<size_t>& obj_i | |||
|             manager.open_gizmo(GLGizmosManager::EType::Simplify); | ||||
|             return true; | ||||
|         }; | ||||
|         notification_manager->push_notification( | ||||
|             NotificationType::SimplifySuggestion, | ||||
|             NotificationManager::NotificationLevel::WarningNotification, | ||||
|             text.str(), hypertext, open_simplify);     | ||||
|         notification_manager->push_object_warning_notification(text.str(), model.objects[object_id]->id(), hypertext, open_simplify); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -3882,9 +3889,9 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this->statusbar()->set_progress(evt.status.percent); | ||||
|         this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…")); | ||||
|         //notification_manager->set_progress_bar_percentage("Slicing progress", (float)evt.status.percent / 100.0f);
 | ||||
| //        this->statusbar()->set_progress(evt.status.percent);
 | ||||
| //        this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…"));
 | ||||
|         notification_manager->set_slicing_progress_percentage(evt.status.text, (float)evt.status.percent / 100.0f); | ||||
|     } | ||||
|     if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) { | ||||
|         switch (this->printer_technology) { | ||||
|  | @ -3935,7 +3942,6 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) | |||
| 
 | ||||
| void Plater::priv::on_slicing_completed(wxCommandEvent & evt) | ||||
| { | ||||
|     notification_manager->push_slicing_complete_notification(evt.GetInt(), is_sidebar_collapsed()); | ||||
|     switch (this->printer_technology) { | ||||
|     case ptFFF: | ||||
|         this->update_fff_scene(); | ||||
|  | @ -3958,8 +3964,8 @@ void Plater::priv::on_export_began(wxCommandEvent& evt) | |||
| void Plater::priv::on_slicing_began() | ||||
| { | ||||
| 	clear_warnings(); | ||||
| 	notification_manager->close_notification_of_type(NotificationType::SlicingComplete); | ||||
|     notification_manager->close_notification_of_type(NotificationType::SignDetected); | ||||
|     notification_manager->close_notification_of_type(NotificationType::ExportFinished); | ||||
| } | ||||
| void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid) | ||||
| { | ||||
|  | @ -3983,6 +3989,16 @@ void Plater::priv::actualize_slicing_warnings(const PrintBase &print) | |||
| 	notification_manager->remove_slicing_warnings_of_released_objects(ids); | ||||
|     notification_manager->set_all_slicing_warnings_gray(true); | ||||
| } | ||||
| void Plater::priv::actualize_object_warnings(const PrintBase& print) | ||||
| { | ||||
|     std::vector<ObjectID> ids; | ||||
|     for (const ModelObject* object : print.model().objects ) | ||||
|     { | ||||
|         ids.push_back(object->id()); | ||||
|     } | ||||
|     std::sort(ids.begin(), ids.end()); | ||||
|     notification_manager->remove_object_warnings_of_released_objects(ids); | ||||
| } | ||||
| void Plater::priv::clear_warnings() | ||||
| { | ||||
| 	notification_manager->close_slicing_errors_and_warnings(); | ||||
|  | @ -4014,8 +4030,9 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) | |||
|     // At this point of time the thread should be either finished or canceled,
 | ||||
|     // so the following call just confirms, that the produced data were consumed.
 | ||||
|     this->background_process.stop(); | ||||
|     this->statusbar()->reset_cancel_callback(); | ||||
|     this->statusbar()->stop_busy(); | ||||
| //    this->statusbar()->reset_cancel_callback();
 | ||||
| //    this->statusbar()->stop_busy();
 | ||||
|     notification_manager->set_slicing_progress_export_possible(); | ||||
| 
 | ||||
|     // Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
 | ||||
|     this->background_process.reset_export(); | ||||
|  | @ -4032,7 +4049,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) | |||
|                 show_error(q, message.first, message.second); | ||||
|         } else | ||||
|             notification_manager->push_slicing_error_notification(message.first); | ||||
|         this->statusbar()->set_status_text(from_u8(message.first)); | ||||
| //        this->statusbar()->set_status_text(from_u8(message.first));
 | ||||
|         if (evt.invalidate_plater()) | ||||
|         { | ||||
|             const wxString invalid_str = _L("Invalid data"); | ||||
|  | @ -4042,8 +4059,10 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) | |||
|         } | ||||
|         has_error = true; | ||||
|     } | ||||
|     if (evt.cancelled()) | ||||
|         this->statusbar()->set_status_text(_L("Cancelled")); | ||||
|     if (evt.cancelled()) { | ||||
| //        this->statusbar()->set_status_text(_L("Cancelled"));
 | ||||
|         this->notification_manager->set_slicing_progress_percentage(_utf8("Slicing Cancelled."), -1); | ||||
|     } | ||||
| 
 | ||||
|     this->sidebar->show_sliced_info_sizer(evt.success()); | ||||
| 
 | ||||
|  | @ -4247,6 +4266,20 @@ void Plater::priv::set_project_filename(const wxString& filename) | |||
|         wxGetApp().mainframe->add_to_recent_projects(filename); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::init_notification_manager() | ||||
| { | ||||
|     if (!notification_manager) | ||||
|         return; | ||||
|     notification_manager->init(); | ||||
| 
 | ||||
|     auto cancel_callback = [this]() { | ||||
|         this->background_process.stop(); | ||||
|     }; | ||||
|     notification_manager->init_slicing_progress_notification(cancel_callback); | ||||
|     notification_manager->set_fff(printer_technology == ptFFF); | ||||
|     notification_manager->init_progress_indicator(); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::set_current_canvas_as_dirty() | ||||
| { | ||||
|     if (current_panel == view3D) | ||||
|  | @ -5685,7 +5718,7 @@ void Plater::export_stl(bool extended, bool selection_only) | |||
|     } | ||||
| 
 | ||||
|     Slic3r::store_stl(path_u8.c_str(), &mesh, true); | ||||
|     p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path)); | ||||
| //    p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path));
 | ||||
| } | ||||
| 
 | ||||
| void Plater::export_amf() | ||||
|  | @ -5702,10 +5735,10 @@ void Plater::export_amf() | |||
|     bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; | ||||
|     if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { | ||||
|         // Success
 | ||||
|         p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path)); | ||||
| //        p->statusbar()->set_status_text(format_wxstr(_L("AMF file exported to %s"), path));
 | ||||
|     } else { | ||||
|         // Failure
 | ||||
|         p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path)); | ||||
| //        p->statusbar()->set_status_text(format_wxstr(_L("Error exporting AMF file %s"), path));
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -5744,12 +5777,12 @@ bool Plater::export_3mf(const boost::filesystem::path& output_path) | |||
|     bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data); | ||||
|     if (ret) { | ||||
|         // Success
 | ||||
|         p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); | ||||
| //        p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path));
 | ||||
|         p->set_project_filename(path); | ||||
|     } | ||||
|     else { | ||||
|         // Failure
 | ||||
|         p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); | ||||
| //        p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path));
 | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
|  | @ -6323,6 +6356,8 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology) | |||
| 
 | ||||
|     p->sidebar->get_searcher().set_printer_technology(printer_technology); | ||||
| 
 | ||||
|     p->notification_manager->set_fff(printer_technology == ptFFF); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
|  | @ -6343,7 +6378,7 @@ void Plater::clear_before_change_mesh(int obj_idx) | |||
|         // snapshot_time is captured by copy so the lambda knows where to undo/redo to.
 | ||||
|         get_notification_manager()->push_notification( | ||||
|                     NotificationType::CustomSupportsAndSeamRemovedAfterRepair, | ||||
|                     NotificationManager::NotificationLevel::RegularNotification, | ||||
|                     NotificationManager::NotificationLevel::RegularNotificationLevel, | ||||
|                     _u8L("Custom supports, seams and multimaterial painting were " | ||||
|                          "removed after repairing the mesh.")); | ||||
| //                    _u8L("Undo the repair"),
 | ||||
|  | @ -6356,7 +6391,7 @@ void Plater::clear_before_change_mesh(int obj_idx) | |||
| //                        else
 | ||||
| //                            notification_manager->push_notification(
 | ||||
| //                                NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
 | ||||
| //                                NotificationManager::NotificationLevel::RegularNotification,
 | ||||
| //                                NotificationManager::NotificationLevel::RegularNotificationLevel,
 | ||||
| //                                _u8L("Cannot undo to before the mesh repair!"));
 | ||||
| //                        return true;
 | ||||
| //                    });
 | ||||
|  | @ -6622,14 +6657,14 @@ Mouse3DController& Plater::get_mouse3d_controller() | |||
|     return p->mouse3d_controller; | ||||
| } | ||||
| 
 | ||||
| const NotificationManager* Plater::get_notification_manager() const | ||||
| std::shared_ptr<NotificationManager> Plater::get_notification_manager() | ||||
| { | ||||
| 	return p->notification_manager; | ||||
| } | ||||
| 
 | ||||
| NotificationManager* Plater::get_notification_manager() | ||||
| void Plater::init_notification_manager() | ||||
| { | ||||
| 	return p->notification_manager; | ||||
|     p->init_notification_manager(); | ||||
| } | ||||
| 
 | ||||
| bool Plater::can_delete() const { return p->can_delete(); } | ||||
|  |  | |||
|  | @ -359,11 +359,11 @@ public: | |||
| 	void set_bed_shape() const; | ||||
|     void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const; | ||||
| 
 | ||||
| 	const NotificationManager* get_notification_manager() const; | ||||
| 	NotificationManager* get_notification_manager(); | ||||
| 	std::shared_ptr<NotificationManager> get_notification_manager(); | ||||
|     void init_notification_manager(); | ||||
| 
 | ||||
|     void bring_instance_forward(); | ||||
| 
 | ||||
|      | ||||
|     // ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
 | ||||
| 	class SuppressSnapshots | ||||
| 	{ | ||||
|  |  | |||
|  | @ -194,3 +194,4 @@ void ProgressStatusBar::hide_cancel_button() | |||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,6 +25,7 @@ namespace Slic3r { | |||
|  * of the Slicer main window. It consists of a message area to the left and a | ||||
|  * progress indication area to the right with an optional cancel button. | ||||
|  */ | ||||
|   | ||||
| class ProgressStatusBar : public ProgressIndicator | ||||
| { | ||||
|     wxStatusBar *self;      // we cheat! It should be the base class but: perl!
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 enricoturri1966
						enricoturri1966