mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-30 20:21:12 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_custom_bed
This commit is contained in:
		
						commit
						610e0f7ee6
					
				
					 56 changed files with 1779 additions and 1171 deletions
				
			
		|  | @ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false | |||
| BreakConstructorInitializers: BeforeComma | ||||
| BreakAfterJavaFieldAnnotations: false | ||||
| BreakStringLiterals: true | ||||
| ColumnLimit:     75 | ||||
| ColumnLimit:     78 | ||||
| CommentPragmas:  '^ IWYU pragma:' | ||||
| CompactNamespaces: true | ||||
| ConstructorInitializerAllOnOneLineOrOnePerLine: true | ||||
|  |  | |||
							
								
								
									
										2
									
								
								deps/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								deps/CMakeLists.txt
									
										
									
									
										vendored
									
									
								
							|  | @ -105,7 +105,7 @@ else() | |||
|         dep_gtest | ||||
|         dep_nlopt | ||||
|         dep_qhull | ||||
|         dep_libigl | ||||
|         # dep_libigl # Not working, static build has different Eigen | ||||
|     ) | ||||
| 
 | ||||
| endif() | ||||
|  |  | |||
							
								
								
									
										
											BIN
										
									
								
								resources/icons/drop_to_bed.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/icons/drop_to_bed.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 528 B | 
|  | @ -15,7 +15,8 @@ const std::string USAGE_STR = { | |||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
| Contour3D create_base_pool(const ExPolygons &ground_layer,  | ||||
| Contour3D create_base_pool(const Polygons &ground_layer,  | ||||
|                            const Polygons &holes = {}, | ||||
|                            const PoolConfig& cfg = PoolConfig()); | ||||
| 
 | ||||
| Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, | ||||
|  | @ -42,37 +43,28 @@ int main(const int argc, const char *argv[]) { | |||
|     model.ReadSTLFile(argv[1]); | ||||
|     model.align_to_origin(); | ||||
| 
 | ||||
|     ExPolygons ground_slice; | ||||
|     sla::Contour3D mesh; | ||||
| //    TriangleMesh basepool;
 | ||||
| 
 | ||||
|     Polygons ground_slice; | ||||
|     sla::base_plate(model, ground_slice, 0.1f); | ||||
| 
 | ||||
|     if(ground_slice.empty()) return EXIT_FAILURE; | ||||
| 
 | ||||
| //    ExPolygon bottom_plate = ground_slice.front();
 | ||||
| //    ExPolygon top_plate = bottom_plate;
 | ||||
| //    sla::offset(top_plate, coord_t(3.0/SCALING_FACTOR));
 | ||||
| //    sla::offset(bottom_plate, coord_t(1.0/SCALING_FACTOR));
 | ||||
|     Polygon gndfirst; gndfirst = ground_slice.front(); | ||||
|     sla::offset_with_breakstick_holes(gndfirst, 0.5, 10, 0.3); | ||||
| 
 | ||||
|     sla::Contour3D mesh; | ||||
| 
 | ||||
| 
 | ||||
|     bench.start(); | ||||
| 
 | ||||
| //    TriangleMesh pool;
 | ||||
|     sla::PoolConfig cfg; | ||||
|     cfg.min_wall_height_mm = 0; | ||||
|     cfg.edge_radius_mm = 0.2; | ||||
|     mesh = sla::create_base_pool(ground_slice, cfg); | ||||
|      | ||||
| //    mesh.merge(triangulate_expolygon_3d(top_plate, 3.0, false));
 | ||||
| //    mesh.merge(triangulate_expolygon_3d(bottom_plate, 0.0, true));
 | ||||
| //    mesh = sla::walls(bottom_plate.contour, top_plate.contour, 0, 3, 2.0, [](){});
 | ||||
|     cfg.edge_radius_mm = 0; | ||||
|     mesh = sla::create_base_pool(ground_slice, {}, cfg); | ||||
| 
 | ||||
|     bench.stop(); | ||||
| 
 | ||||
|     cout << "Base pool creation time: " << std::setprecision(10) | ||||
|          << bench.getElapsedSec() << " seconds." << endl; | ||||
| 
 | ||||
| //    auto point = []()
 | ||||
|     for(auto& trind : mesh.indices) { | ||||
|         Vec3d p0 = mesh.points[size_t(trind[0])]; | ||||
|         Vec3d p1 = mesh.points[size_t(trind[1])]; | ||||
|  |  | |||
|  | @ -586,6 +586,9 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|     } | ||||
|     m_analyzer.set_extruder_offsets(extruder_offsets); | ||||
| 
 | ||||
|     // tell analyzer about the gcode flavor
 | ||||
|     m_analyzer.set_gcode_flavor(print.config().gcode_flavor); | ||||
| 
 | ||||
|     // resets analyzer's tracking data
 | ||||
|     m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; | ||||
|     m_last_width = GCodeAnalyzer::Default_Width; | ||||
|  | @ -1057,6 +1060,10 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|     print.m_print_statistics.clear(); | ||||
|     print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); | ||||
|     print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; | ||||
|     print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(); | ||||
|     if (m_silent_time_estimator_enabled) | ||||
|         print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(); | ||||
| 
 | ||||
|     std::vector<Extruder> extruders = m_writer.extruders(); | ||||
|     if (! extruders.empty()) { | ||||
|         std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0); | ||||
|  |  | |||
|  | @ -106,6 +106,11 @@ void GCodeAnalyzer::set_extruder_offsets(const GCodeAnalyzer::ExtruderOffsetsMap | |||
|     m_extruder_offsets = extruder_offsets; | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::set_gcode_flavor(const GCodeFlavor& flavor) | ||||
| { | ||||
|     m_gcode_flavor = flavor; | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::reset() | ||||
| { | ||||
|     _set_units(Millimeters); | ||||
|  | @ -249,6 +254,14 @@ void GCodeAnalyzer::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLi | |||
|                         _processM83(line); | ||||
|                         break; | ||||
|                     } | ||||
|                 case 108: | ||||
|                 case 135: | ||||
|                     { | ||||
|                         // these are used by MakerWare and Sailfish firmwares
 | ||||
|                         // for tool changing - we can process it in one place
 | ||||
|                         _processM108orM135(line); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
|  | @ -426,9 +439,27 @@ void GCodeAnalyzer::_processM600(const GCodeReader::GCodeLine& line) | |||
|     _set_cp_color_id(m_state.cur_cp_color_id); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) | ||||
| void GCodeAnalyzer::_processM108orM135(const GCodeReader::GCodeLine& line) | ||||
| { | ||||
|     // These M-codes are used by MakerWare and Sailfish to change active tool.
 | ||||
|     // They have to be processed otherwise toolchanges will be unrecognised
 | ||||
|     // by the analyzer - see https://github.com/prusa3d/PrusaSlicer/issues/2566
 | ||||
| 
 | ||||
|     size_t code = ::atoi(&(line.cmd()[1])); | ||||
|     if ((code == 108 && m_gcode_flavor == gcfSailfish) | ||||
|      || (code == 135 && m_gcode_flavor == gcfMakerWare)) { | ||||
| 
 | ||||
|         std::string cmd = line.raw(); | ||||
|         size_t T_pos = cmd.find("T"); | ||||
|         if (T_pos != std::string::npos) { | ||||
|             cmd = cmd.substr(T_pos); | ||||
|             _processT(cmd); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_processT(const std::string& cmd) | ||||
| { | ||||
|     std::string cmd = line.cmd(); | ||||
|     if (cmd.length() > 1) | ||||
|     { | ||||
|         unsigned int id = (unsigned int)::strtol(cmd.substr(1).c_str(), nullptr, 10); | ||||
|  | @ -442,6 +473,11 @@ void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_processT(const GCodeReader::GCodeLine& line) | ||||
| { | ||||
|     _processT(line.cmd()); | ||||
| } | ||||
| 
 | ||||
| bool GCodeAnalyzer::_process_tags(const GCodeReader::GCodeLine& line) | ||||
| { | ||||
|     std::string comment = line.comment(); | ||||
|  |  | |||
|  | @ -106,6 +106,7 @@ private: | |||
|     GCodeReader m_parser; | ||||
|     TypeToMovesMap m_moves_map; | ||||
|     ExtruderOffsetsMap m_extruder_offsets; | ||||
|     GCodeFlavor m_gcode_flavor; | ||||
| 
 | ||||
|     // The output of process_layer()
 | ||||
|     std::string m_process_output; | ||||
|  | @ -115,6 +116,8 @@ public: | |||
| 
 | ||||
|     void set_extruder_offsets(const ExtruderOffsetsMap& extruder_offsets); | ||||
| 
 | ||||
|     void set_gcode_flavor(const GCodeFlavor& flavor); | ||||
| 
 | ||||
|     // Reinitialize the analyzer
 | ||||
|     void reset(); | ||||
| 
 | ||||
|  | @ -164,10 +167,14 @@ private: | |||
|     // Set extruder to relative mode
 | ||||
|     void _processM83(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Set tool (MakerWare and Sailfish flavor)
 | ||||
|     void _processM108orM135(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Set color change
 | ||||
|     void _processM600(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Processes T line (Select Tool)
 | ||||
|     void _processT(const std::string& command); | ||||
|     void _processT(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|     // Processes the tags
 | ||||
|  |  | |||
|  | @ -466,7 +466,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: | |||
|         } | ||||
|     case Extrusion::Tool: | ||||
|         { | ||||
|             unsigned int tools_colors_count = tool_colors.size() / 4; | ||||
|             unsigned int tools_colors_count = (unsigned int)tool_colors.size() / 4; | ||||
|             items.reserve(tools_colors_count); | ||||
|             for (unsigned int i = 0; i < tools_colors_count; ++i) | ||||
|             { | ||||
|  | @ -491,17 +491,20 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: | |||
|                     items.emplace_back(Slic3r::I18N::translate(L("Default print color")), color); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 std::string id_str = std::to_string(i + 1) + ": "; | ||||
| 
 | ||||
|                 if (i == 0) { | ||||
|                     items.emplace_back((boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); | ||||
|                     items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("up to %.2f mm"))) % cp_values[0].first).str(), color); | ||||
|                     break; | ||||
|                 } | ||||
|                 if (i == color_print_cnt) { | ||||
|                     items.emplace_back((boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i-1].second).str(), color); | ||||
|                     items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("above %.2f mm"))) % cp_values[i - 1].second).str(), color); | ||||
|                     continue; | ||||
|                 } | ||||
| 
 | ||||
| //                 items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) %  cp_values[i-1] % cp_values[i]).str(), color);
 | ||||
|                 items.emplace_back((boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) %  cp_values[i-1].second % cp_values[i].first).str(), color); | ||||
|                 items.emplace_back(id_str + (boost::format(Slic3r::I18N::translate(L("%.2f - %.2f mm"))) % cp_values[i - 1].second% cp_values[i].first).str(), color); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|  |  | |||
|  | @ -174,7 +174,7 @@ namespace Slic3r { | |||
|     const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; SILENT_LAST_M73_OUTPUT_PLACEHOLDER"; | ||||
| 
 | ||||
|     GCodeTimeEstimator::GCodeTimeEstimator(EMode mode) | ||||
|         : _mode(mode) | ||||
|         : m_mode(mode) | ||||
|     { | ||||
|         reset(); | ||||
|         set_default(); | ||||
|  | @ -183,7 +183,7 @@ namespace Slic3r { | |||
|     void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         _parser.parse_line(gcode_line,  | ||||
|         m_parser.parse_line(gcode_line,  | ||||
|             [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) | ||||
|         { this->_process_gcode_line(reader, line); }); | ||||
|     } | ||||
|  | @ -196,7 +196,7 @@ namespace Slic3r { | |||
|         { this->_process_gcode_line(reader, line); }; | ||||
|         for (; *ptr != 0;) { | ||||
|             gline.reset(); | ||||
|             ptr = _parser.parse_line(ptr, gline, action); | ||||
|             ptr = m_parser.parse_line(ptr, gline, action); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -206,10 +206,13 @@ namespace Slic3r { | |||
|         if (start_from_beginning) | ||||
|         { | ||||
|             _reset_time(); | ||||
|             _last_st_synchronized_block_id = -1; | ||||
|             m_last_st_synchronized_block_id = -1; | ||||
|         } | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -219,12 +222,15 @@ namespace Slic3r { | |||
|     { | ||||
|         reset(); | ||||
| 
 | ||||
|         _parser.parse_buffer(gcode, | ||||
|         m_parser.parse_buffer(gcode, | ||||
|             [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) | ||||
|         { this->_process_gcode_line(reader, line); }); | ||||
| 
 | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -234,9 +240,12 @@ namespace Slic3r { | |||
|     { | ||||
|         reset(); | ||||
| 
 | ||||
|         _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); | ||||
|         m_parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2)); | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -249,9 +258,12 @@ namespace Slic3r { | |||
|         auto action = [this](GCodeReader &reader, const GCodeReader::GCodeLine &line) | ||||
|         { this->_process_gcode_line(reader, line); }; | ||||
|         for (const std::string& line : gcode_lines) | ||||
|             _parser.parse_line(line, action); | ||||
|             m_parser.parse_line(line, action); | ||||
|         _calculate_time(); | ||||
| 
 | ||||
|         if (m_needs_color_times && (m_color_time_cache != 0.0f)) | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         _log_moves_stats(); | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
|  | @ -270,7 +282,7 @@ namespace Slic3r { | |||
|             throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n")); | ||||
| 
 | ||||
|         std::string time_mask; | ||||
|         switch (_mode) | ||||
|         switch (m_mode) | ||||
|         { | ||||
|         default: | ||||
|         case Normal: | ||||
|  | @ -291,7 +303,7 @@ namespace Slic3r { | |||
|         // buffer line to export only when greater than 64K to reduce writing calls
 | ||||
|         std::string export_line; | ||||
|         char time_line[64]; | ||||
| 		G1LineIdToBlockIdMap::const_iterator it_line_id = _g1_line_ids.begin(); | ||||
| 		G1LineIdToBlockIdMap::const_iterator it_line_id = m_g1_line_ids.begin(); | ||||
| 		while (std::getline(in, gcode_line)) | ||||
|         { | ||||
|             if (!in.good()) | ||||
|  | @ -301,15 +313,15 @@ namespace Slic3r { | |||
|             } | ||||
| 
 | ||||
|             // replaces placeholders for initial line M73 with the real lines
 | ||||
|             if (((_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || | ||||
|                 ((_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) | ||||
|             if (((m_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) || | ||||
|                 ((m_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))) | ||||
|             { | ||||
|                 sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(_time).c_str()); | ||||
|                 sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(m_time).c_str()); | ||||
|                 gcode_line = time_line; | ||||
|             } | ||||
|             // replaces placeholders for final line M73 with the real lines
 | ||||
|             else if (((_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || | ||||
|                      ((_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) | ||||
|             else if (((m_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) || | ||||
|                      ((m_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))) | ||||
|             { | ||||
|                 sprintf(time_line, time_mask.c_str(), "100", "0"); | ||||
|                 gcode_line = time_line; | ||||
|  | @ -319,27 +331,27 @@ namespace Slic3r { | |||
| 
 | ||||
| 
 | ||||
|             // add remaining time lines where needed
 | ||||
|             _parser.parse_line(gcode_line, | ||||
|             m_parser.parse_line(gcode_line, | ||||
|                 [this, &it_line_id, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line) | ||||
|             { | ||||
|                 if (line.cmd_is("G1")) | ||||
|                 { | ||||
|                     ++g1_lines_count; | ||||
| 
 | ||||
| 					assert(it_line_id == _g1_line_ids.end() || it_line_id->first >= g1_lines_count); | ||||
| 					assert(it_line_id == m_g1_line_ids.end() || it_line_id->first >= g1_lines_count); | ||||
| 
 | ||||
| 					const Block *block = nullptr; | ||||
| 					if (it_line_id != _g1_line_ids.end() && it_line_id->first == g1_lines_count) { | ||||
| 						if (line.has_e() && it_line_id->second < (unsigned int)_blocks.size()) | ||||
| 							block = &_blocks[it_line_id->second]; | ||||
| 					if (it_line_id != m_g1_line_ids.end() && it_line_id->first == g1_lines_count) { | ||||
| 						if (line.has_e() && it_line_id->second < (unsigned int)m_blocks.size()) | ||||
| 							block = &m_blocks[it_line_id->second]; | ||||
| 						++it_line_id; | ||||
| 					} | ||||
| 
 | ||||
| 					if (block != nullptr && block->elapsed_time != -1.0f) { | ||||
|                         float block_remaining_time = _time - block->elapsed_time; | ||||
|                         float block_remaining_time = m_time - block->elapsed_time; | ||||
|                         if (std::abs(last_recorded_time - block_remaining_time) > interval) | ||||
|                         { | ||||
|                             sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / _time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); | ||||
|                             sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / m_time)).c_str(), _get_time_minutes(block_remaining_time).c_str()); | ||||
|                             gcode_line += time_line; | ||||
| 
 | ||||
|                             last_recorded_time = block_remaining_time; | ||||
|  | @ -387,240 +399,240 @@ namespace Slic3r { | |||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_position(EAxis axis, float position) | ||||
|     { | ||||
|         _state.axis[axis].position = position; | ||||
|         m_state.axis[axis].position = position; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.axis[axis].max_feedrate = feedrate_mm_sec; | ||||
|         m_state.axis[axis].max_feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration) | ||||
|     { | ||||
|         _state.axis[axis].max_acceleration = acceleration; | ||||
|         m_state.axis[axis].max_acceleration = acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk) | ||||
|     { | ||||
|         _state.axis[axis].max_jerk = jerk; | ||||
|         m_state.axis[axis].max_jerk = jerk; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_position(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].position; | ||||
|         return m_state.axis[axis].position; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].max_feedrate; | ||||
|         return m_state.axis[axis].max_feedrate; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].max_acceleration; | ||||
|         return m_state.axis[axis].max_acceleration; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const | ||||
|     { | ||||
|         return _state.axis[axis].max_jerk; | ||||
|         return m_state.axis[axis].max_jerk; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.feedrate = feedrate_mm_sec; | ||||
|         m_state.feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_feedrate() const | ||||
|     { | ||||
|         return _state.feedrate; | ||||
|         return m_state.feedrate; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2) | ||||
|     { | ||||
|         _state.acceleration = (_state.max_acceleration == 0) ?  | ||||
|         m_state.acceleration = (m_state.max_acceleration == 0) ?  | ||||
|             acceleration_mm_sec2 :  | ||||
|             // Clamp the acceleration with the maximum.
 | ||||
|             std::min(_state.max_acceleration, acceleration_mm_sec2); | ||||
|             std::min(m_state.max_acceleration, acceleration_mm_sec2); | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_acceleration() const | ||||
|     { | ||||
|         return _state.acceleration; | ||||
|         return m_state.acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_max_acceleration(float acceleration_mm_sec2) | ||||
|     { | ||||
|         _state.max_acceleration = acceleration_mm_sec2; | ||||
|         m_state.max_acceleration = acceleration_mm_sec2; | ||||
|         if (acceleration_mm_sec2 > 0) | ||||
|             _state.acceleration = acceleration_mm_sec2; | ||||
|             m_state.acceleration = acceleration_mm_sec2; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_max_acceleration() const | ||||
|     { | ||||
|         return _state.max_acceleration; | ||||
|         return m_state.max_acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2) | ||||
|     { | ||||
|         _state.retract_acceleration = acceleration_mm_sec2; | ||||
|         m_state.retract_acceleration = acceleration_mm_sec2; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_retract_acceleration() const | ||||
|     { | ||||
|         return _state.retract_acceleration; | ||||
|         return m_state.retract_acceleration; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.minimum_feedrate = feedrate_mm_sec; | ||||
|         m_state.minimum_feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_minimum_feedrate() const | ||||
|     { | ||||
|         return _state.minimum_feedrate; | ||||
|         return m_state.minimum_feedrate; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec) | ||||
|     { | ||||
|         _state.minimum_travel_feedrate = feedrate_mm_sec; | ||||
|         m_state.minimum_travel_feedrate = feedrate_mm_sec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_minimum_travel_feedrate() const | ||||
|     { | ||||
|         return _state.minimum_travel_feedrate; | ||||
|         return m_state.minimum_travel_feedrate; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_filament_load_times(const std::vector<double> &filament_load_times) | ||||
|     { | ||||
|         _state.filament_load_times.clear(); | ||||
|         m_state.filament_load_times.clear(); | ||||
|         for (double t : filament_load_times) | ||||
|             _state.filament_load_times.push_back((float)t); | ||||
|             m_state.filament_load_times.push_back((float)t); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_filament_unload_times(const std::vector<double> &filament_unload_times) | ||||
|     { | ||||
|         _state.filament_unload_times.clear(); | ||||
|         m_state.filament_unload_times.clear(); | ||||
|         for (double t : filament_unload_times) | ||||
|             _state.filament_unload_times.push_back((float)t); | ||||
|             m_state.filament_unload_times.push_back((float)t); | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_filament_load_time(unsigned int id_extruder) | ||||
|     { | ||||
|         return | ||||
|             (_state.filament_load_times.empty() || id_extruder == _state.extruder_id_unloaded) ?  | ||||
|             (m_state.filament_load_times.empty() || id_extruder == m_state.extruder_id_unloaded) ?  | ||||
|                 0 : | ||||
|                 (_state.filament_load_times.size() <= id_extruder) ? | ||||
|                     _state.filament_load_times.front() :  | ||||
|                     _state.filament_load_times[id_extruder]; | ||||
|                 (m_state.filament_load_times.size() <= id_extruder) ? | ||||
|                     m_state.filament_load_times.front() :  | ||||
|                     m_state.filament_load_times[id_extruder]; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_filament_unload_time(unsigned int id_extruder) | ||||
|     { | ||||
|         return | ||||
|             (_state.filament_unload_times.empty() || id_extruder == _state.extruder_id_unloaded) ?  | ||||
|             (m_state.filament_unload_times.empty() || id_extruder == m_state.extruder_id_unloaded) ?  | ||||
|                 0 : | ||||
|                 (_state.filament_unload_times.size() <= id_extruder) ? | ||||
|                     _state.filament_unload_times.front() :  | ||||
|                     _state.filament_unload_times[id_extruder]; | ||||
|                 (m_state.filament_unload_times.size() <= id_extruder) ? | ||||
|                     m_state.filament_unload_times.front() :  | ||||
|                     m_state.filament_unload_times[id_extruder]; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_extrude_factor_override_percentage(float percentage) | ||||
|     { | ||||
|         _state.extrude_factor_override_percentage = percentage; | ||||
|         m_state.extrude_factor_override_percentage = percentage; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_extrude_factor_override_percentage() const | ||||
|     { | ||||
|         return _state.extrude_factor_override_percentage; | ||||
|         return m_state.extrude_factor_override_percentage; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_dialect(GCodeFlavor dialect) | ||||
|     { | ||||
|         _state.dialect = dialect; | ||||
|         m_state.dialect = dialect; | ||||
|     } | ||||
| 
 | ||||
|     GCodeFlavor GCodeTimeEstimator::get_dialect() const | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         return _state.dialect; | ||||
|         return m_state.dialect; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units) | ||||
|     { | ||||
|         _state.units = units; | ||||
|         m_state.units = units; | ||||
|     } | ||||
| 
 | ||||
|     GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const | ||||
|     { | ||||
|         return _state.units; | ||||
|         return m_state.units; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_global_positioning_type(GCodeTimeEstimator::EPositioningType type) | ||||
|     { | ||||
|         _state.global_positioning_type = type; | ||||
|         m_state.global_positioning_type = type; | ||||
|     } | ||||
| 
 | ||||
|     GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_global_positioning_type() const | ||||
|     { | ||||
|         return _state.global_positioning_type; | ||||
|         return m_state.global_positioning_type; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_e_local_positioning_type(GCodeTimeEstimator::EPositioningType type) | ||||
|     { | ||||
|         _state.e_local_positioning_type = type; | ||||
|         m_state.e_local_positioning_type = type; | ||||
|     } | ||||
| 
 | ||||
|     GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_e_local_positioning_type() const | ||||
|     { | ||||
|         return _state.e_local_positioning_type; | ||||
|         return m_state.e_local_positioning_type; | ||||
|     } | ||||
| 
 | ||||
|     int GCodeTimeEstimator::get_g1_line_id() const | ||||
|     { | ||||
|         return _state.g1_line_id; | ||||
|         return m_state.g1_line_id; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::increment_g1_line_id() | ||||
|     { | ||||
|         ++_state.g1_line_id; | ||||
|         ++m_state.g1_line_id; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::reset_g1_line_id() | ||||
|     { | ||||
|         _state.g1_line_id = 0; | ||||
|         m_state.g1_line_id = 0; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_extruder_id(unsigned int id) | ||||
|     { | ||||
|         _state.extruder_id = id; | ||||
|         m_state.extruder_id = id; | ||||
|     } | ||||
| 
 | ||||
|     unsigned int GCodeTimeEstimator::get_extruder_id() const | ||||
|     { | ||||
|         return _state.extruder_id; | ||||
|         return m_state.extruder_id; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::reset_extruder_id() | ||||
|     { | ||||
|         // Set the initial extruder ID to unknown. For the multi-material setup it means
 | ||||
|         // that all the filaments are parked in the MMU and no filament is loaded yet.
 | ||||
|         _state.extruder_id = _state.extruder_id_unloaded; | ||||
|         m_state.extruder_id = m_state.extruder_id_unloaded; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::add_additional_time(float timeSec) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         _state.additional_time += timeSec; | ||||
|         m_state.additional_time += timeSec; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_additional_time(float timeSec) | ||||
|     { | ||||
|         _state.additional_time = timeSec; | ||||
|         m_state.additional_time = timeSec; | ||||
|     } | ||||
| 
 | ||||
|     float GCodeTimeEstimator::get_additional_time() const | ||||
|     { | ||||
|         return _state.additional_time; | ||||
|         return m_state.additional_time; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::set_default() | ||||
|  | @ -648,8 +660,8 @@ namespace Slic3r { | |||
|             set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]); | ||||
|         } | ||||
| 
 | ||||
|         _state.filament_load_times.clear(); | ||||
|         _state.filament_unload_times.clear(); | ||||
|         m_state.filament_load_times.clear(); | ||||
|         m_state.filament_unload_times.clear(); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::reset() | ||||
|  | @ -664,7 +676,7 @@ namespace Slic3r { | |||
| 
 | ||||
|     float GCodeTimeEstimator::get_time() const | ||||
|     { | ||||
|         return _time; | ||||
|         return m_time; | ||||
|     } | ||||
| 
 | ||||
|     std::string GCodeTimeEstimator::get_time_dhms() const | ||||
|  | @ -677,19 +689,44 @@ namespace Slic3r { | |||
|         return _get_time_minutes(get_time()); | ||||
|     } | ||||
| 
 | ||||
|     std::vector<float> GCodeTimeEstimator::get_color_times() const | ||||
|     { | ||||
|         return m_color_times; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::string> GCodeTimeEstimator::get_color_times_dhms() const | ||||
|     { | ||||
|         std::vector<std::string> ret; | ||||
|         for (float t : m_color_times) | ||||
|         { | ||||
|             ret.push_back(_get_time_dhms(t)); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     std::vector<std::string> GCodeTimeEstimator::get_color_times_minutes() const | ||||
|     { | ||||
|         std::vector<std::string> ret; | ||||
|         for (float t : m_color_times) | ||||
|         { | ||||
|             ret.push_back(_get_time_minutes(t)); | ||||
|         } | ||||
|         return ret; | ||||
|     } | ||||
| 
 | ||||
|     // Return an estimate of the memory consumed by the time estimator.
 | ||||
| 	size_t GCodeTimeEstimator::memory_used() const | ||||
|     { | ||||
|         size_t out = sizeof(*this); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->_blocks, Block); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->_g1_line_ids, G1LineIdToBlockId); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->m_blocks, Block); | ||||
| 		out += SLIC3R_STDVEC_MEMSIZE(this->m_g1_line_ids, G1LineIdToBlockId); | ||||
|         return out; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_reset() | ||||
|     { | ||||
|         _curr.reset(); | ||||
|         _prev.reset(); | ||||
|         m_curr.reset(); | ||||
|         m_prev.reset(); | ||||
| 
 | ||||
|         set_axis_position(X, 0.0f); | ||||
|         set_axis_position(Y, 0.0f); | ||||
|  | @ -701,19 +738,23 @@ namespace Slic3r { | |||
| 
 | ||||
|         reset_extruder_id(); | ||||
|         reset_g1_line_id(); | ||||
|         _g1_line_ids.clear(); | ||||
|         m_g1_line_ids.clear(); | ||||
| 
 | ||||
|         _last_st_synchronized_block_id = -1; | ||||
|         m_last_st_synchronized_block_id = -1; | ||||
| 
 | ||||
|         m_needs_color_times = false; | ||||
|         m_color_times.clear(); | ||||
|         m_color_time_cache = 0.0f; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_reset_time() | ||||
|     { | ||||
|         _time = 0.0f; | ||||
|         m_time = 0.0f; | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_reset_blocks() | ||||
|     { | ||||
|         _blocks.clear(); | ||||
|         m_blocks.clear(); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_calculate_time() | ||||
|  | @ -723,35 +764,32 @@ namespace Slic3r { | |||
|         _reverse_pass(); | ||||
|         _recalculate_trapezoids(); | ||||
| 
 | ||||
|         _time += get_additional_time(); | ||||
|         m_time += get_additional_time(); | ||||
|         m_color_time_cache += get_additional_time(); | ||||
| 
 | ||||
|         for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) | ||||
|         for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) | ||||
|         { | ||||
|             Block& block = _blocks[i]; | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|             Block& block = m_blocks[i]; | ||||
|             float block_time = 0.0f; | ||||
|             block_time += block.acceleration_time(); | ||||
|             block_time += block.cruise_time(); | ||||
|             block_time += block.deceleration_time(); | ||||
|             _time += block_time; | ||||
|             block.elapsed_time = _time; | ||||
|             m_time += block_time; | ||||
|             block.elapsed_time = m_time; | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|             MovesStatsMap::iterator it = _moves_stats.find(block.move_type); | ||||
|             if (it == _moves_stats.end()) | ||||
|                 it = _moves_stats.insert(MovesStatsMap::value_type(block.move_type, MoveStats())).first; | ||||
| 
 | ||||
|             it->second.count += 1; | ||||
|             it->second.time += block_time; | ||||
| #else | ||||
|             _time += block.acceleration_time(); | ||||
|             _time += block.cruise_time(); | ||||
|             _time += block.deceleration_time(); | ||||
|             block.elapsed_time = _time; | ||||
| #endif // ENABLE_MOVE_STATS
 | ||||
| 
 | ||||
|             m_color_time_cache += block_time; | ||||
|         } | ||||
| 
 | ||||
|         _last_st_synchronized_block_id = (int)_blocks.size() - 1; | ||||
|         m_last_st_synchronized_block_id = (int)m_blocks.size() - 1; | ||||
|         // The additional time has been consumed (added to the total time), reset it to zero.
 | ||||
|         set_additional_time(0.); | ||||
|     } | ||||
|  | @ -866,6 +904,11 @@ namespace Slic3r { | |||
|                             _processM566(line); | ||||
|                             break; | ||||
|                         } | ||||
|                     case 600: // Set color change
 | ||||
|                         { | ||||
|                             _processM600(line); | ||||
|                             break; | ||||
|                         } | ||||
|                     case 702: // MK3 MMU2: Process the final filament unload.
 | ||||
|                         { | ||||
|                             _processM702(line); | ||||
|  | @ -934,7 +977,7 @@ namespace Slic3r { | |||
|             return; | ||||
| 
 | ||||
|         // calculates block feedrate
 | ||||
|         _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); | ||||
|         m_curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate()); | ||||
| 
 | ||||
|         float distance = block.move_length(); | ||||
|         float invDistance = 1.0f / distance; | ||||
|  | @ -942,23 +985,23 @@ namespace Slic3r { | |||
|         float min_feedrate_factor = 1.0f; | ||||
|         for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|         { | ||||
|             _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance; | ||||
|             m_curr.axis_feedrate[a] = m_curr.feedrate * block.delta_pos[a] * invDistance; | ||||
|             if (a == E) | ||||
|                 _curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); | ||||
|                 m_curr.axis_feedrate[a] *= get_extrude_factor_override_percentage(); | ||||
| 
 | ||||
|             _curr.abs_axis_feedrate[a] = std::abs(_curr.axis_feedrate[a]); | ||||
|             if (_curr.abs_axis_feedrate[a] > 0.0f) | ||||
|                 min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]); | ||||
|             m_curr.abs_axis_feedrate[a] = std::abs(m_curr.axis_feedrate[a]); | ||||
|             if (m_curr.abs_axis_feedrate[a] > 0.0f) | ||||
|                 min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / m_curr.abs_axis_feedrate[a]); | ||||
|         } | ||||
|          | ||||
|         block.feedrate.cruise = min_feedrate_factor * _curr.feedrate; | ||||
|         block.feedrate.cruise = min_feedrate_factor * m_curr.feedrate; | ||||
| 
 | ||||
|         if (min_feedrate_factor < 1.0f) | ||||
|         { | ||||
|             for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|             { | ||||
|                 _curr.axis_feedrate[a] *= min_feedrate_factor; | ||||
|                 _curr.abs_axis_feedrate[a] *= min_feedrate_factor; | ||||
|                 m_curr.axis_feedrate[a] *= min_feedrate_factor; | ||||
|                 m_curr.abs_axis_feedrate[a] *= min_feedrate_factor; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -975,25 +1018,25 @@ namespace Slic3r { | |||
|         block.acceleration = acceleration; | ||||
| 
 | ||||
|         // calculates block exit feedrate
 | ||||
|         _curr.safe_feedrate = block.feedrate.cruise; | ||||
|         m_curr.safe_feedrate = block.feedrate.cruise; | ||||
| 
 | ||||
|         for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|         { | ||||
|             float axis_max_jerk = get_axis_max_jerk((EAxis)a); | ||||
|             if (_curr.abs_axis_feedrate[a] > axis_max_jerk) | ||||
|                 _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk); | ||||
|             if (m_curr.abs_axis_feedrate[a] > axis_max_jerk) | ||||
|                 m_curr.safe_feedrate = std::min(m_curr.safe_feedrate, axis_max_jerk); | ||||
|         } | ||||
| 
 | ||||
|         block.feedrate.exit = _curr.safe_feedrate; | ||||
|         block.feedrate.exit = m_curr.safe_feedrate; | ||||
| 
 | ||||
|         // calculates block entry feedrate
 | ||||
|         float vmax_junction = _curr.safe_feedrate; | ||||
|         if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) | ||||
|         float vmax_junction = m_curr.safe_feedrate; | ||||
|         if (!m_blocks.empty() && (m_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD)) | ||||
|         { | ||||
|             bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise; | ||||
|             float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise); | ||||
|             bool prev_speed_larger = m_prev.feedrate > block.feedrate.cruise; | ||||
|             float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / m_prev.feedrate) : (m_prev.feedrate / block.feedrate.cruise); | ||||
|             // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
 | ||||
|             vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate; | ||||
|             vmax_junction = prev_speed_larger ? block.feedrate.cruise : m_prev.feedrate; | ||||
| 
 | ||||
|             float v_factor = 1.0f; | ||||
|             bool limited = false; | ||||
|  | @ -1001,8 +1044,8 @@ namespace Slic3r { | |||
|             for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|             { | ||||
|                 // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop.
 | ||||
|                 float v_exit = _prev.axis_feedrate[a]; | ||||
|                 float v_entry = _curr.axis_feedrate[a]; | ||||
|                 float v_exit = m_prev.axis_feedrate[a]; | ||||
|                 float v_entry = m_curr.axis_feedrate[a]; | ||||
| 
 | ||||
|                 if (prev_speed_larger) | ||||
|                     v_exit *= smaller_speed_factor; | ||||
|  | @ -1044,23 +1087,23 @@ namespace Slic3r { | |||
|             float vmax_junction_threshold = vmax_junction * 0.99f; | ||||
| 
 | ||||
|             // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start.
 | ||||
|             if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold)) | ||||
|                 vmax_junction = _curr.safe_feedrate; | ||||
|             if ((m_prev.safe_feedrate > vmax_junction_threshold) && (m_curr.safe_feedrate > vmax_junction_threshold)) | ||||
|                 vmax_junction = m_curr.safe_feedrate; | ||||
|         } | ||||
| 
 | ||||
|         float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance); | ||||
|         float v_allowable = Block::max_allowable_speed(-acceleration, m_curr.safe_feedrate, distance); | ||||
|         block.feedrate.entry = std::min(vmax_junction, v_allowable); | ||||
| 
 | ||||
|         block.max_entry_speed = vmax_junction; | ||||
|         block.flags.nominal_length = (block.feedrate.cruise <= v_allowable); | ||||
|         block.flags.recalculate = true; | ||||
|         block.safe_feedrate = _curr.safe_feedrate; | ||||
|         block.safe_feedrate = m_curr.safe_feedrate; | ||||
| 
 | ||||
|         // calculates block trapezoid
 | ||||
|         block.calculate_trapezoid(); | ||||
| 
 | ||||
|         // updates previous
 | ||||
|         _prev = _curr; | ||||
|         m_prev = m_curr; | ||||
| 
 | ||||
|         // updates axis positions
 | ||||
|         for (unsigned char a = X; a < Num_Axis; ++a) | ||||
|  | @ -1091,8 +1134,8 @@ namespace Slic3r { | |||
| #endif // ENABLE_MOVE_STATS
 | ||||
| 
 | ||||
|         // adds block to blocks list
 | ||||
|         _blocks.emplace_back(block); | ||||
|         _g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)_blocks.size() - 1)); | ||||
|         m_blocks.emplace_back(block); | ||||
|         m_g1_line_ids.emplace_back(G1LineIdToBlockIdMap::value_type(get_g1_line_id(), (unsigned int)m_blocks.size() - 1)); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line) | ||||
|  | @ -1336,6 +1379,18 @@ namespace Slic3r { | |||
|             set_axis_max_jerk(E, line.e() * MMMIN_TO_MMSEC); | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_processM600(const GCodeReader::GCodeLine& line) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         m_needs_color_times = true; | ||||
|         _calculate_time(); | ||||
|         if (m_color_time_cache != 0.0f) | ||||
|         { | ||||
|             m_color_times.push_back(m_color_time_cache); | ||||
|             m_color_time_cache = 0.0f; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void GCodeTimeEstimator::_processM702(const GCodeReader::GCodeLine& line) | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|  | @ -1376,11 +1431,11 @@ namespace Slic3r { | |||
|     void GCodeTimeEstimator::_forward_pass() | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         if (_blocks.size() > 1) | ||||
|         if (m_blocks.size() > 1) | ||||
|         { | ||||
|             for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size() - 1; ++i) | ||||
|             for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size() - 1; ++i) | ||||
|             { | ||||
|                 _planner_forward_pass_kernel(_blocks[i], _blocks[i + 1]); | ||||
|                 _planner_forward_pass_kernel(m_blocks[i], m_blocks[i + 1]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1388,11 +1443,11 @@ namespace Slic3r { | |||
|     void GCodeTimeEstimator::_reverse_pass() | ||||
|     { | ||||
|         PROFILE_FUNC(); | ||||
|         if (_blocks.size() > 1) | ||||
|         if (m_blocks.size() > 1) | ||||
|         { | ||||
|             for (int i = (int)_blocks.size() - 1; i >= _last_st_synchronized_block_id + 2; --i) | ||||
|             for (int i = (int)m_blocks.size() - 1; i >= m_last_st_synchronized_block_id + 2; --i) | ||||
|             { | ||||
|                 _planner_reverse_pass_kernel(_blocks[i - 1], _blocks[i]); | ||||
|                 _planner_reverse_pass_kernel(m_blocks[i - 1], m_blocks[i]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -1444,9 +1499,9 @@ namespace Slic3r { | |||
|         Block* curr = nullptr; | ||||
|         Block* next = nullptr; | ||||
| 
 | ||||
|         for (int i = _last_st_synchronized_block_id + 1; i < (int)_blocks.size(); ++i) | ||||
|         for (int i = m_last_st_synchronized_block_id + 1; i < (int)m_blocks.size(); ++i) | ||||
|         { | ||||
|             Block& b = _blocks[i]; | ||||
|             Block& b = m_blocks[i]; | ||||
| 
 | ||||
|             curr = next; | ||||
|             next = &b; | ||||
|  | @ -1517,7 +1572,7 @@ namespace Slic3r { | |||
|         { | ||||
|             std::cout << MOVE_TYPE_STR[move.first]; | ||||
|             std::cout << ": count " << move.second.count << " (" << 100.0f * (float)move.second.count / moves_count << "%)"; | ||||
|             std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / _time << "%)"; | ||||
|             std::cout << " - time: " << move.second.time << "s (" << 100.0f * move.second.time / m_time << "%)"; | ||||
|             std::cout << std::endl; | ||||
|         } | ||||
|         std::cout << std::endl; | ||||
|  |  | |||
|  | @ -215,17 +215,22 @@ namespace Slic3r { | |||
|         typedef std::vector<G1LineIdToBlockId> G1LineIdToBlockIdMap; | ||||
| 
 | ||||
|     private: | ||||
|         EMode _mode; | ||||
|         GCodeReader _parser; | ||||
|         State _state; | ||||
|         Feedrates _curr; | ||||
|         Feedrates _prev; | ||||
|         BlocksList _blocks; | ||||
|         EMode m_mode; | ||||
|         GCodeReader m_parser; | ||||
|         State m_state; | ||||
|         Feedrates m_curr; | ||||
|         Feedrates m_prev; | ||||
|         BlocksList m_blocks; | ||||
|         // Map between g1 line id and blocks id, used to speed up export of remaining times
 | ||||
|         G1LineIdToBlockIdMap _g1_line_ids; | ||||
|         G1LineIdToBlockIdMap m_g1_line_ids; | ||||
|         // Index of the last block already st_synchronized
 | ||||
|         int _last_st_synchronized_block_id; | ||||
|         float _time; // s
 | ||||
|         int m_last_st_synchronized_block_id; | ||||
|         float m_time; // s
 | ||||
| 
 | ||||
|         // data to calculate color print times
 | ||||
|         bool m_needs_color_times; | ||||
|         std::vector<float> m_color_times; | ||||
|         float m_color_time_cache; | ||||
| 
 | ||||
| #if ENABLE_MOVE_STATS | ||||
|         MovesStatsMap _moves_stats; | ||||
|  | @ -341,6 +346,15 @@ namespace Slic3r { | |||
|         // Returns the estimated time, in minutes (integer)
 | ||||
|         std::string get_time_minutes() const; | ||||
| 
 | ||||
|        // Returns the estimated time, in seconds, for each color
 | ||||
|         std::vector<float> get_color_times() const; | ||||
| 
 | ||||
|         // Returns the estimated time, in format DDd HHh MMm SSs, for each color
 | ||||
|         std::vector<std::string> get_color_times_dhms() const; | ||||
| 
 | ||||
|         // Returns the estimated time, in minutes (integer), for each color
 | ||||
|         std::vector<std::string> get_color_times_minutes() const; | ||||
| 
 | ||||
|         // Return an estimate of the memory consumed by the time estimator.
 | ||||
|         size_t memory_used() const; | ||||
| 
 | ||||
|  | @ -409,6 +423,9 @@ namespace Slic3r { | |||
|         // Set allowable instantaneous speed change
 | ||||
|         void _processM566(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|         // Set color change
 | ||||
|         void _processM600(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|         // Unload the current filament into the MK3 MMU2 unit at the end of print.
 | ||||
|         void _processM702(const GCodeReader::GCodeLine& line); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,11 +1,13 @@ | |||
| #ifndef MTUTILS_HPP | ||||
| #define MTUTILS_HPP | ||||
| 
 | ||||
| #include <atomic>     // for std::atomic_flag and memory orders
 | ||||
| #include <mutex>      // for std::lock_guard
 | ||||
| #include <functional> // for std::function
 | ||||
| #include <utility>    // for std::forward
 | ||||
| #include <atomic>       // for std::atomic_flag and memory orders
 | ||||
| #include <mutex>        // for std::lock_guard
 | ||||
| #include <functional>   // for std::function
 | ||||
| #include <utility>      // for std::forward
 | ||||
| #include <vector> | ||||
| #include <algorithm> | ||||
| #include <cmath> | ||||
| 
 | ||||
| #include "libslic3r.h" | ||||
| #include "Point.hpp" | ||||
|  | @ -242,6 +244,58 @@ template<class C> bool all_of(const C &container) | |||
|                        }); | ||||
| } | ||||
| 
 | ||||
| template<class T> struct remove_cvref | ||||
| { | ||||
|     using type = | ||||
|         typename std::remove_cv<typename std::remove_reference<T>::type>::type; | ||||
| }; | ||||
| 
 | ||||
| template<class T> using remove_cvref_t = typename remove_cvref<T>::type; | ||||
| 
 | ||||
| template<template<class> class C, class T> | ||||
| class Container : public C<remove_cvref_t<T>> | ||||
| { | ||||
| public: | ||||
|     explicit Container(size_t count, T &&initval) | ||||
|         : C<remove_cvref_t<T>>(count, initval) | ||||
|     {} | ||||
| }; | ||||
| 
 | ||||
| template<class T> using DefaultContainer = std::vector<T>; | ||||
| 
 | ||||
| /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html
 | ||||
| template<class T, class I, template<class> class C = DefaultContainer> | ||||
| inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n) | ||||
| { | ||||
|     Container<C, T> vals(n, T()); | ||||
| 
 | ||||
|     T      stride = (stop - start) / n; | ||||
|     size_t i      = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride; | ||||
|     }); | ||||
| 
 | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| /// A set of equidistant values starting from 'start' (inclusive), ending
 | ||||
| /// in the closest multiple of 'stride' less than or equal to 'end' and
 | ||||
| /// leaving 'stride' space between each value. 
 | ||||
| /// Very similar to Matlab [start:stride:end] notation.
 | ||||
| template<class T, template<class> class C = DefaultContainer> | ||||
| inline C<remove_cvref_t<T>> grid(const T &start, const T &stop, const T &stride) | ||||
| { | ||||
|     Container<C, T> vals(size_t(std::ceil((stop - start) / stride)), T()); | ||||
|      | ||||
|     int i = 0; | ||||
|     std::generate(vals.begin(), vals.end(), [&i, start, stride] { | ||||
|         return start + i++ * stride;  | ||||
|     }); | ||||
|       | ||||
|     return vals; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // A shorter C++14 style form of the enable_if metafunction
 | ||||
| template<bool B, class T> | ||||
| using enable_if_t = typename std::enable_if<B, T>::type; | ||||
|  | @ -304,7 +358,7 @@ inline SLIC3R_CONSTEXPR ScaledCoordOnly<Tout> scaled(const Tin &v) SLIC3R_NOEXCE | |||
| template<class Tout = coord_t, class Tin, int N, class = FloatingOnly<Tin>> | ||||
| inline EigenVec<ArithmeticOnly<Tout>, N> scaled(const EigenVec<Tin, N> &v) | ||||
| { | ||||
|     return v.template cast<Tout>() / SCALING_FACTOR; | ||||
|     return (v / SCALING_FACTOR).template cast<Tout>(); | ||||
| } | ||||
| 
 | ||||
| // Conversion from arithmetic scaled type to floating point unscaled
 | ||||
|  |  | |||
|  | @ -241,6 +241,8 @@ struct PrintStatistics | |||
|     PrintStatistics() { clear(); } | ||||
|     std::string                     estimated_normal_print_time; | ||||
|     std::string                     estimated_silent_print_time; | ||||
|     std::vector<std::string>        estimated_normal_color_print_times; | ||||
|     std::vector<std::string>        estimated_silent_color_print_times; | ||||
|     double                          total_used_filament; | ||||
|     double                          total_extruded_volume; | ||||
|     double                          total_cost; | ||||
|  | @ -259,6 +261,8 @@ struct PrintStatistics | |||
|     void clear() { | ||||
|         estimated_normal_print_time.clear(); | ||||
|         estimated_silent_print_time.clear(); | ||||
|         estimated_normal_color_print_times.clear(); | ||||
|         estimated_silent_color_print_times.clear(); | ||||
|         total_used_filament    = 0.; | ||||
|         total_extruded_volume  = 0.; | ||||
|         total_cost             = 0.; | ||||
|  |  | |||
|  | @ -2505,6 +2505,19 @@ void PrintConfigDef::init_sla_params() | |||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionFloat(1.0)); | ||||
|      | ||||
|     def = this->add("support_base_safety_distance", coFloat); | ||||
|     def->label = L("Support base safety distance"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip  = L( | ||||
|         "The minimum distance of the pillar base from the model in mm. " | ||||
|         "Makes sense in zero elevation mode where a gap according " | ||||
|         "to this parameter is inserted between the model and the pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->max = 10; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(1)); | ||||
| 
 | ||||
|     def = this->add("support_critical_angle", coFloat); | ||||
|     def->label = L("Critical angle"); | ||||
|     def->category = L("Supports"); | ||||
|  | @ -2537,7 +2550,9 @@ void PrintConfigDef::init_sla_params() | |||
|     def = this->add("support_object_elevation", coFloat); | ||||
|     def->label = L("Object elevation"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("How much the supports should lift up the supported object."); | ||||
|     def->tooltip = L("How much the supports should lift up the supported object. " | ||||
|                      "If this value is zero, the bottom of the model geometry " | ||||
|                      "will be considered as part of the pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->max = 150; // This is the max height of print on SL1
 | ||||
|  | @ -2623,6 +2638,47 @@ void PrintConfigDef::init_sla_params() | |||
|     def->max = 90; | ||||
|     def->mode = comAdvanced; | ||||
|     def->set_default_value(new ConfigOptionFloat(45.0)); | ||||
|      | ||||
|     def = this->add("pad_object_gap", coFloat); | ||||
|     def->label = L("Pad object gap"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip  = L("The gap between the object bottom and the generated " | ||||
|                       "pad in zero elevation mode."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->max = 10; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(1)); | ||||
|      | ||||
|     def = this->add("pad_object_connector_stride", coFloat); | ||||
|     def->label = L("Pad object connector stride"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip = L("Distance between two connector sticks between " | ||||
|                      "the object pad and the generated pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(10)); | ||||
|      | ||||
|     def = this->add("pad_object_connector_width", coFloat); | ||||
|     def->label = L("Pad object connector width"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip  = L("The width of the connectors sticks which connect the " | ||||
|                       "object pad and the generated pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(0.5)); | ||||
|      | ||||
|     def = this->add("pad_object_connector_penetration", coFloat); | ||||
|     def->label = L("Pad object connector penetration"); | ||||
|     def->category = L("Pad"); | ||||
|     def->tooltip  = L( | ||||
|         "How much should the tiny connectors penetrate into the model body."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->min = 0; | ||||
|     def->mode = comExpert; | ||||
|     def->set_default_value(new ConfigOptionFloat(0.3)); | ||||
| } | ||||
| 
 | ||||
| void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) | ||||
|  |  | |||
|  | @ -984,6 +984,9 @@ public: | |||
|     // The height of the pillar base cone in mm.
 | ||||
|     ConfigOptionFloat support_base_height /*= 1.0*/; | ||||
|      | ||||
|     // The minimum distance of the pillar base from the model in mm.
 | ||||
|     ConfigOptionFloat support_base_safety_distance; /*= 1.0*/; | ||||
| 
 | ||||
|     // The default angle for connecting support sticks and junctions.
 | ||||
|     ConfigOptionFloat support_critical_angle /*= 45*/; | ||||
| 
 | ||||
|  | @ -1022,6 +1025,26 @@ public: | |||
|     // The slope of the pad wall...
 | ||||
|     ConfigOptionFloat pad_wall_slope; | ||||
|      | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Zero elevation mode parameters:
 | ||||
|     //    - The object pad will be derived from the the model geometry.
 | ||||
|     //    - There will be a gap between the object pad and the generated pad
 | ||||
|     //      according to the support_base_safety_distance parameter.
 | ||||
|     //    - The two pads will be connected with tiny connector sticks
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|      | ||||
|     // This is the gap between the object bottom and the generated pad
 | ||||
|     ConfigOptionFloat pad_object_gap; | ||||
|      | ||||
|     // How far to place the connector sticks on the object pad perimeter
 | ||||
|     ConfigOptionFloat pad_object_connector_stride; | ||||
|      | ||||
|     // The width of the connectors sticks
 | ||||
|     ConfigOptionFloat pad_object_connector_width; | ||||
|      | ||||
|     // How much should the tiny connectors penetrate into the model body
 | ||||
|     ConfigOptionFloat pad_object_connector_penetration; | ||||
| 
 | ||||
| protected: | ||||
|     void initialize(StaticCacheBase &cache, const char *base_ptr) | ||||
|     { | ||||
|  | @ -1038,6 +1061,7 @@ protected: | |||
|         OPT_PTR(support_pillar_widening_factor); | ||||
|         OPT_PTR(support_base_diameter); | ||||
|         OPT_PTR(support_base_height); | ||||
|         OPT_PTR(support_base_safety_distance); | ||||
|         OPT_PTR(support_critical_angle); | ||||
|         OPT_PTR(support_max_bridge_length); | ||||
|         OPT_PTR(support_max_pillar_link_distance); | ||||
|  | @ -1050,6 +1074,10 @@ protected: | |||
|         OPT_PTR(pad_max_merge_distance); | ||||
|         OPT_PTR(pad_edge_radius); | ||||
|         OPT_PTR(pad_wall_slope); | ||||
|         OPT_PTR(pad_object_gap); | ||||
|         OPT_PTR(pad_object_connector_stride); | ||||
|         OPT_PTR(pad_object_connector_width); | ||||
|         OPT_PTR(pad_object_connector_penetration); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,9 +8,9 @@ | |||
| #include "MTUtils.hpp" | ||||
| 
 | ||||
| // For debugging:
 | ||||
| //#include <fstream>
 | ||||
| //#include <libnest2d/tools/benchmark.h>
 | ||||
| //#include "SVG.hpp"
 | ||||
| // #include <fstream>
 | ||||
| // #include <libnest2d/tools/benchmark.h>
 | ||||
| // #include "SVG.hpp"
 | ||||
| 
 | ||||
| namespace Slic3r { namespace sla { | ||||
| 
 | ||||
|  | @ -184,9 +184,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, | |||
| } | ||||
| 
 | ||||
| /// Offsetting with clipper and smoothing the edges into a curvature.
 | ||||
| void offset(ExPolygon& sh, coord_t distance) { | ||||
| void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { | ||||
|     using ClipperLib::ClipperOffset; | ||||
|     using ClipperLib::jtRound; | ||||
|     using ClipperLib::jtMiter; | ||||
|     using ClipperLib::etClosedPolygon; | ||||
|     using ClipperLib::Paths; | ||||
|     using ClipperLib::Path; | ||||
|  | @ -203,11 +204,13 @@ void offset(ExPolygon& sh, coord_t distance) { | |||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto jointype = edgerounding? jtRound : jtMiter; | ||||
|      | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = scaled<double>(0.01); | ||||
|     Paths result; | ||||
|     offs.AddPath(ctour, jtRound, etClosedPolygon); | ||||
|     offs.AddPaths(holes, jtRound, etClosedPolygon); | ||||
|     offs.AddPath(ctour, jointype, etClosedPolygon); | ||||
|     offs.AddPaths(holes, jointype, etClosedPolygon); | ||||
|     offs.Execute(result, static_cast<double>(distance)); | ||||
| 
 | ||||
|     // Offsetting reverts the orientation and also removes the last vertex
 | ||||
|  | @ -237,6 +240,50 @@ void offset(ExPolygon& sh, coord_t distance) { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void offset(Polygon &sh, coord_t distance, bool edgerounding = true) | ||||
| { | ||||
|     using ClipperLib::ClipperOffset; | ||||
|     using ClipperLib::jtRound; | ||||
|     using ClipperLib::jtMiter; | ||||
|     using ClipperLib::etClosedPolygon; | ||||
|     using ClipperLib::Paths; | ||||
|     using ClipperLib::Path; | ||||
| 
 | ||||
|     auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); | ||||
| 
 | ||||
|     // If the input is not at least a triangle, we can not do this algorithm
 | ||||
|     if (ctour.size() < 3) { | ||||
|         BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ClipperOffset offs; | ||||
|     offs.ArcTolerance = 0.01 * scaled(1.); | ||||
|     Paths result; | ||||
|     offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); | ||||
|     offs.Execute(result, static_cast<double>(distance)); | ||||
| 
 | ||||
|     // Offsetting reverts the orientation and also removes the last vertex
 | ||||
|     // so boost will not have a closed polygon.
 | ||||
| 
 | ||||
|     bool found_the_contour = false; | ||||
|     for (auto &r : result) { | ||||
|         if (ClipperLib::Orientation(r)) { | ||||
|             // We don't like if the offsetting generates more than one contour
 | ||||
|             // but throwing would be an overkill. Instead, we should warn the
 | ||||
|             // caller about the inability to create correct geometries
 | ||||
|             if (!found_the_contour) { | ||||
|                 auto rr = ClipperPath_to_Slic3rPolygon(r); | ||||
|                 sh.points.swap(rr.points); | ||||
|                 found_the_contour = true; | ||||
|             } else { | ||||
|                 BOOST_LOG_TRIVIAL(warning) | ||||
|                     << "Warning: offsetting result is invalid!"; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Unification of polygons (with clipper) preserving holes as well.
 | ||||
| ExPolygons unify(const ExPolygons& shapes) { | ||||
|     using ClipperLib::ptSubject; | ||||
|  | @ -307,6 +354,116 @@ ExPolygons unify(const ExPolygons& shapes) { | |||
|     return retv; | ||||
| } | ||||
| 
 | ||||
| Polygons unify(const Polygons& shapes) { | ||||
|     using ClipperLib::ptSubject; | ||||
|      | ||||
|     bool closed = true; | ||||
|     bool valid = true; | ||||
| 
 | ||||
|     ClipperLib::Clipper clipper; | ||||
| 
 | ||||
|     for(auto& path : shapes) { | ||||
|         auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); | ||||
| 
 | ||||
|         if(!clipperpath.empty()) | ||||
|             valid &= clipper.AddPath(clipperpath, ptSubject, closed); | ||||
|     } | ||||
| 
 | ||||
|     if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; | ||||
| 
 | ||||
|     ClipperLib::Paths result; | ||||
|     clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); | ||||
| 
 | ||||
|     Polygons ret; | ||||
|     for (ClipperLib::Path &p : result) { | ||||
|         Polygon pp = ClipperPath_to_Slic3rPolygon(p); | ||||
|         if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| // Function to cut tiny connector cavities for a given polygon. The input poly
 | ||||
| // will be offsetted by "padding" and small rectangle shaped cavities will be
 | ||||
| // inserted along the perimeter in every "stride" distance. The stick rectangles
 | ||||
| // will have a with about "stick_width". The input dimensions are in world 
 | ||||
| // measure, not the scaled clipper units.
 | ||||
| void breakstick_holes(ExPolygon& poly, | ||||
|                       double padding, | ||||
|                       double stride, | ||||
|                       double stick_width, | ||||
|                       double penetration) | ||||
| {    | ||||
|     // SVG svg("bridgestick_plate.svg");
 | ||||
|     // svg.draw(poly);
 | ||||
| 
 | ||||
|     auto transf = [stick_width, penetration, padding, stride](Points &pts) { | ||||
|         // The connector stick will be a small rectangle with dimensions
 | ||||
|         // stick_width x (penetration + padding) to have some penetration
 | ||||
|         // into the input polygon.
 | ||||
| 
 | ||||
|         Points out; | ||||
|         out.reserve(2 * pts.size()); // output polygon points
 | ||||
| 
 | ||||
|         // stick bottom and right edge dimensions
 | ||||
|         double sbottom = scaled(stick_width); | ||||
|         double sright  = scaled(penetration + padding); | ||||
| 
 | ||||
|         // scaled stride distance
 | ||||
|         double sstride = scaled(stride); | ||||
|         double t       = 0; | ||||
| 
 | ||||
|         // process pairs of vertices as an edge, start with the last and
 | ||||
|         // first point
 | ||||
|         for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { | ||||
|             // Get vertices and the direction vectors
 | ||||
|             const Point &a = pts[i], &b = pts[j]; | ||||
|             Vec2d        dir = b.cast<double>() - a.cast<double>(); | ||||
|             double       nrm = dir.norm(); | ||||
|             dir /= nrm; | ||||
|             Vec2d dirp(-dir(Y), dir(X)); | ||||
| 
 | ||||
|             // Insert start point
 | ||||
|             out.emplace_back(a); | ||||
| 
 | ||||
|             // dodge the start point, do not make sticks on the joins
 | ||||
|             while (t < sbottom) t += sbottom; | ||||
|             double tend = nrm - sbottom; | ||||
| 
 | ||||
|             while (t < tend) { // insert the stick on the polygon perimeter
 | ||||
| 
 | ||||
|                 // calculate the stick rectangle vertices and insert them
 | ||||
|                 // into the output.
 | ||||
|                 Point p1 = a + (t * dir).cast<coord_t>(); | ||||
|                 Point p2 = p1 + (sright * dirp).cast<coord_t>(); | ||||
|                 Point p3 = p2 + (sbottom * dir).cast<coord_t>(); | ||||
|                 Point p4 = p3 + (sright * -dirp).cast<coord_t>(); | ||||
|                 out.insert(out.end(), {p1, p2, p3, p4}); | ||||
| 
 | ||||
|                 // continue along the perimeter
 | ||||
|                 t += sstride; | ||||
|             } | ||||
| 
 | ||||
|             t = t - nrm; | ||||
| 
 | ||||
|             // Insert edge endpoint
 | ||||
|             out.emplace_back(b); | ||||
|         } | ||||
|          | ||||
|         // move the new points
 | ||||
|         out.shrink_to_fit(); | ||||
|         pts.swap(out); | ||||
|     }; | ||||
|      | ||||
|     if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { | ||||
|         transf(poly.contour.points); | ||||
|         for (auto &h : poly.holes) transf(h.points); | ||||
|     } | ||||
|      | ||||
|     // svg.draw(poly);
 | ||||
|     // svg.Close();
 | ||||
| } | ||||
| 
 | ||||
| /// This method will create a rounded edge around a flat polygon in 3d space.
 | ||||
| /// 'base_plate' parameter is the target plate.
 | ||||
| /// 'radius' is the radius of the edges.
 | ||||
|  | @ -426,40 +583,37 @@ inline Point centroid(Points& pp) { | |||
|     return c; | ||||
| } | ||||
| 
 | ||||
| inline Point centroid(const ExPolygon& poly) { | ||||
|     return poly.contour.centroid(); | ||||
| inline Point centroid(const Polygon& poly) { | ||||
|     return poly.centroid(); | ||||
| } | ||||
| 
 | ||||
| /// A fake concave hull that is constructed by connecting separate shapes
 | ||||
| /// with explicit bridges. Bridges are generated from each shape's centroid
 | ||||
| /// to the center of the "scene" which is the centroid calculated from the shape
 | ||||
| /// centroids (a star is created...)
 | ||||
| ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | ||||
|                         ThrowOnCancel throw_on_cancel = [](){}) | ||||
| Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, | ||||
|                       ThrowOnCancel throw_on_cancel = [](){}) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     using SpatElement = std::pair<BoundingBox, unsigned>; | ||||
|     using SpatElement = std::pair<Point, unsigned>; | ||||
|     using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; | ||||
| 
 | ||||
|     if(polys.empty()) return ExPolygons(); | ||||
|     if(polys.empty()) return Polygons(); | ||||
|      | ||||
|     ExPolygons punion = unify(polys);   // could be redundant
 | ||||
|     const double max_dist = scaled(max_dist_mm); | ||||
| 
 | ||||
|     Polygons punion = unify(polys);   // could be redundant
 | ||||
| 
 | ||||
|     if(punion.size() == 1) return punion; | ||||
| 
 | ||||
|     // We get the centroids of all the islands in the 2D slice
 | ||||
|     Points centroids; centroids.reserve(punion.size()); | ||||
|     std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), | ||||
|                    [](const ExPolygon& poly) { return centroid(poly); }); | ||||
| 
 | ||||
| 
 | ||||
|     SpatIndex boxindex; unsigned idx = 0; | ||||
|     std::for_each(punion.begin(), punion.end(), | ||||
|                   [&boxindex, &idx](const ExPolygon& expo) { | ||||
|         BoundingBox bb(expo); | ||||
|         boxindex.insert(std::make_pair(bb, idx++)); | ||||
|     }); | ||||
|                    [](const Polygon& poly) { return centroid(poly); }); | ||||
| 
 | ||||
|     SpatIndex ctrindex; | ||||
|     unsigned  idx = 0; | ||||
|     for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); | ||||
|      | ||||
|     // Centroid of the centroids of islands. This is where the additional
 | ||||
|     // connector sticks are routed.
 | ||||
|  | @ -470,25 +624,32 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | |||
|     idx = 0; | ||||
|     std::transform(centroids.begin(), centroids.end(), | ||||
|                    std::back_inserter(punion), | ||||
|                    [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] | ||||
|                    [¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel] | ||||
|                    (const Point& c) | ||||
|     { | ||||
|         throw_on_cancel(); | ||||
|         double dx = x(c) - x(cc), dy = y(c) - y(cc); | ||||
|         double l = std::sqrt(dx * dx + dy * dy); | ||||
|         double nx = dx / l, ny = dy / l; | ||||
|         double max_dist = scaled<double>(max_dist_mm); | ||||
|          | ||||
|         ExPolygon& expo = punion[idx++]; | ||||
|         BoundingBox querybb(expo); | ||||
|         Point& ct = centroids[idx]; | ||||
|          | ||||
|         querybb.offset(max_dist); | ||||
|         std::vector<SpatElement> result; | ||||
|         boxindex.query(bgi::intersects(querybb), std::back_inserter(result)); | ||||
|         if(result.size() <= 1) return ExPolygon(); | ||||
|         ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); | ||||
| 
 | ||||
|         ExPolygon r; | ||||
|         auto& ctour = r.contour.points; | ||||
|         double dist = max_dist; | ||||
|         for (const SpatElement &el : result) | ||||
|             if (el.second != idx) { | ||||
|                 dist = Line(el.first, ct).length(); | ||||
|                 break; | ||||
|             } | ||||
|          | ||||
|         idx++; | ||||
|          | ||||
|         if (dist >= max_dist) return Polygon(); | ||||
|          | ||||
|         Polygon r; | ||||
|         auto& ctour = r.points; | ||||
| 
 | ||||
|         ctour.reserve(3); | ||||
|         ctour.emplace_back(cc); | ||||
|  | @ -507,20 +668,16 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, | |||
|     return punion; | ||||
| } | ||||
| 
 | ||||
| void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, | ||||
|                 float layerh, ThrowOnCancel thrfn) | ||||
| void base_plate(const TriangleMesh &      mesh, | ||||
|                 ExPolygons &              output, | ||||
|                 const std::vector<float> &heights, | ||||
|                 ThrowOnCancel             thrfn) | ||||
| { | ||||
|     TriangleMesh m = mesh; | ||||
|     m.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|     TriangleMeshSlicer slicer(&m); | ||||
|     if (mesh.empty()) return; | ||||
|     //    m.require_shared_vertices(); // TriangleMeshSlicer needs this    
 | ||||
|     TriangleMeshSlicer slicer(&mesh); | ||||
|      | ||||
|     auto bb = mesh.bounding_box(); | ||||
|     float gnd = float(bb.min(Z)); | ||||
|     std::vector<float> heights = {float(bb.min(Z))}; | ||||
|     for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) | ||||
|         heights.emplace_back(hi); | ||||
| 
 | ||||
|     std::vector<ExPolygons> out; out.reserve(size_t(std::ceil(h/layerh))); | ||||
|     std::vector<ExPolygons> out; out.reserve(heights.size()); | ||||
|     slicer.slice(heights, 0.f, &out, thrfn); | ||||
|      | ||||
|     size_t count = 0; for(auto& o : out) count += o.size(); | ||||
|  | @ -542,7 +699,24 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, | |||
|     } | ||||
| } | ||||
| 
 | ||||
| Contour3D create_base_pool(const ExPolygons &ground_layer,  | ||||
| void base_plate(const TriangleMesh &mesh, | ||||
|                 ExPolygons &        output, | ||||
|                 float               h, | ||||
|                 float               layerh, | ||||
|                 ThrowOnCancel       thrfn) | ||||
| { | ||||
|     auto bb = mesh.bounding_box(); | ||||
|     float gnd = float(bb.min(Z)); | ||||
|     std::vector<float> heights = {float(bb.min(Z))}; | ||||
|      | ||||
|     for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) | ||||
|         heights.emplace_back(hi); | ||||
|      | ||||
|     base_plate(mesh, output, heights, thrfn); | ||||
| } | ||||
| 
 | ||||
| Contour3D create_base_pool(const Polygons &ground_layer,  | ||||
|                            const ExPolygons &obj_self_pad = {}, | ||||
|                            const PoolConfig& cfg = PoolConfig())  | ||||
| { | ||||
|     // for debugging:
 | ||||
|  | @ -557,7 +731,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|     // serve as the bottom plate of the pad. We will offset this concave hull
 | ||||
|     // and then offset back the result with clipper with rounding edges ON. This
 | ||||
|     // trick will create a nice rounded pad shape.
 | ||||
|     ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); | ||||
|     Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); | ||||
| 
 | ||||
|     const double thickness      = cfg.min_wall_thickness_mm; | ||||
|     const double wingheight     = cfg.min_wall_height_mm; | ||||
|  | @ -577,42 +751,37 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
| 
 | ||||
|     Contour3D pool; | ||||
| 
 | ||||
|     for(ExPolygon& concaveh : concavehs) { | ||||
|         if(concaveh.contour.points.empty()) return pool; | ||||
| 
 | ||||
|         // Get rid of any holes in the concave hull output.
 | ||||
|         concaveh.holes.clear(); | ||||
|     for(Polygon& concaveh : concavehs) { | ||||
|         if(concaveh.points.empty()) return pool; | ||||
| 
 | ||||
|         // Here lies the trick that does the smoothing only with clipper offset
 | ||||
|         // calls. The offset is configured to round edges. Inner edges will
 | ||||
|         // be rounded because we offset twice: ones to get the outer (top) plate
 | ||||
|         // and again to get the inner (bottom) plate
 | ||||
|         auto outer_base = concaveh; | ||||
|         outer_base.holes.clear(); | ||||
|         offset(outer_base, s_safety_dist + s_wingdist + s_thickness); | ||||
| 
 | ||||
|         ExPolygon bottom_poly = outer_base; | ||||
|         bottom_poly.holes.clear(); | ||||
|         ExPolygon bottom_poly; bottom_poly.contour = outer_base; | ||||
|         offset(bottom_poly, -s_bottom_offs); | ||||
| 
 | ||||
|         // Punching a hole in the top plate for the cavity
 | ||||
|         ExPolygon top_poly; | ||||
|         ExPolygon middle_base; | ||||
|         ExPolygon inner_base; | ||||
|         top_poly.contour = outer_base.contour; | ||||
|         top_poly.contour = outer_base; | ||||
| 
 | ||||
|         if(wingheight > 0) { | ||||
|             inner_base = outer_base; | ||||
|             inner_base.contour = outer_base; | ||||
|             offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); | ||||
| 
 | ||||
|             middle_base = outer_base; | ||||
|             middle_base.contour = outer_base; | ||||
|             offset(middle_base, -s_thickness); | ||||
|             top_poly.holes.emplace_back(middle_base.contour); | ||||
|             auto& tph = top_poly.holes.back().points; | ||||
|             std::reverse(tph.begin(), tph.end()); | ||||
|         } | ||||
| 
 | ||||
|         ExPolygon ob = outer_base; double wh = 0; | ||||
|         ExPolygon ob; ob.contour = outer_base; double wh = 0; | ||||
| 
 | ||||
|         // now we will calculate the angle or portion of the circle from
 | ||||
|         // pi/2 that will connect perfectly with the bottom plate.
 | ||||
|  | @ -659,6 +828,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|         if(wingheight > 0) { | ||||
|             // Generate the smoothed edge geometry
 | ||||
|             wh = 0; | ||||
|             ob = middle_base; | ||||
|             if(s_eradius) pool.merge(round_edges(middle_base, | ||||
|                                    r, | ||||
|                                    phi - 90, // from tangent lines
 | ||||
|  | @ -673,11 +843,59 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|                              wh, -wingdist, thrcl)); | ||||
|         } | ||||
| 
 | ||||
|         // Now we need to triangulate the top and bottom plates as well as the
 | ||||
|         // cavity bottom plate which is the same as the bottom plate but it is
 | ||||
|         // elevated by the thickness.
 | ||||
|         if (cfg.embed_object) { | ||||
|             ExPolygons bttms = diff_ex(to_polygons(bottom_poly), | ||||
|                                        to_polygons(obj_self_pad)); | ||||
|              | ||||
|             assert(!bttms.empty()); | ||||
|              | ||||
|             std::sort(bttms.begin(), bttms.end(), | ||||
|                       [](const ExPolygon& e1, const ExPolygon& e2) { | ||||
|                           return e1.contour.area() > e2.contour.area(); | ||||
|                       }); | ||||
|              | ||||
|             if(wingheight > 0) inner_base.holes = bttms.front().holes; | ||||
|             else top_poly.holes = bttms.front().holes; | ||||
| 
 | ||||
|             auto straight_walls = | ||||
|                 [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { | ||||
|                      | ||||
|                 auto lines = cntr.lines(); | ||||
|                  | ||||
|                 for (auto &l : lines) { | ||||
|                     auto s = coord_t(pool.points.size()); | ||||
|                     auto& pts = pool.points; | ||||
|                     pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); | ||||
|                     pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); | ||||
|                     pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); | ||||
|                     pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); | ||||
|                      | ||||
|                     pool.indices.emplace_back(s, s + 1, s + 3); | ||||
|                     pool.indices.emplace_back(s, s + 3, s + 2); | ||||
|                 } | ||||
|             }; | ||||
|              | ||||
|             coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight); | ||||
|             for (ExPolygon &ep : bttms) { | ||||
|                 pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); | ||||
|                 for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); | ||||
|             } | ||||
|              | ||||
|             // Skip the outer contour, triangulate the holes
 | ||||
|             for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { | ||||
|                 pool.merge(triangulate_expolygon_3d(*it, -wingheight)); | ||||
|                 straight_walls(it->contour, z_lo, z_hi); | ||||
|             } | ||||
|              | ||||
|         } else { | ||||
|             // Now we need to triangulate the top and bottom plates as well as
 | ||||
|             // the cavity bottom plate which is the same as the bottom plate
 | ||||
|             // but it is elevated by the thickness.
 | ||||
|              | ||||
|             pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); | ||||
|         } | ||||
|          | ||||
|         pool.merge(triangulate_expolygon_3d(top_poly)); | ||||
|         pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); | ||||
| 
 | ||||
|         if(wingheight > 0) | ||||
|             pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); | ||||
|  | @ -687,8 +905,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, | |||
|     return pool; | ||||
| } | ||||
| 
 | ||||
| void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, | ||||
|                       const PoolConfig& cfg) | ||||
| void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, | ||||
|                       const ExPolygons &holes, const PoolConfig& cfg) | ||||
| { | ||||
|      | ||||
| 
 | ||||
|  | @ -698,7 +916,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, | |||
|     // std::fstream fout("pad_debug.obj", std::fstream::out);
 | ||||
|     // if(fout.good()) pool.to_obj(fout);
 | ||||
| 
 | ||||
|     out.merge(mesh(create_base_pool(ground_layer, cfg))); | ||||
|     out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -8,7 +8,9 @@ | |||
| namespace Slic3r { | ||||
| 
 | ||||
| class ExPolygon; | ||||
| class Polygon; | ||||
| using ExPolygons = std::vector<ExPolygon>; | ||||
| using Polygons = std::vector<Polygon>; | ||||
| 
 | ||||
| class TriangleMesh; | ||||
| 
 | ||||
|  | @ -19,16 +21,40 @@ using ThrowOnCancel = std::function<void(void)>; | |||
| /// Calculate the polygon representing the silhouette from the specified height
 | ||||
| void base_plate(const TriangleMesh& mesh,       // input mesh
 | ||||
|                 ExPolygons& output,             // Output will be merged with
 | ||||
|                 float zlevel = 0.1f,            // Plate creation level
 | ||||
|                 float samplingheight = 0.1f,    // The height range to sample
 | ||||
|                 float layerheight = 0.05f,      // The sampling height
 | ||||
|                 ThrowOnCancel thrfn = [](){});  // Will be called frequently
 | ||||
| 
 | ||||
| void base_plate(const TriangleMesh& mesh,       // input mesh
 | ||||
|                 ExPolygons& output,             // Output will be merged with
 | ||||
|                 const std::vector<float>&,      // Exact Z levels to sample
 | ||||
|                 ThrowOnCancel thrfn = [](){});  // Will be called frequently
 | ||||
| 
 | ||||
| // Function to cut tiny connector cavities for a given polygon. The input poly
 | ||||
| // will be offsetted by "padding" and small rectangle shaped cavities will be
 | ||||
| // inserted along the perimeter in every "stride" distance. The stick rectangles
 | ||||
| // will have a with about "stick_width". The input dimensions are in world 
 | ||||
| // measure, not the scaled clipper units.
 | ||||
| void breakstick_holes(ExPolygon &poly, | ||||
|                       double     padding, | ||||
|                       double     stride, | ||||
|                       double     stick_width, | ||||
|                       double     penetration = 0.0); | ||||
| 
 | ||||
| struct PoolConfig { | ||||
|     double min_wall_thickness_mm = 2; | ||||
|     double min_wall_height_mm = 5; | ||||
|     double max_merge_distance_mm = 50; | ||||
|     double edge_radius_mm = 1; | ||||
|     double wall_slope = std::atan(1.0);          // Universal constant for Pi/4
 | ||||
|     struct EmbedObject { | ||||
|         double object_gap_mm = 0.5; | ||||
|         double stick_stride_mm = 10; | ||||
|         double stick_width_mm = 0.3; | ||||
|         double stick_penetration_mm = 0.1; | ||||
|         bool enabled = false; | ||||
|         operator bool() const { return enabled; } | ||||
|     } embed_object; | ||||
| 
 | ||||
|     ThrowOnCancel throw_on_cancel = [](){}; | ||||
| 
 | ||||
|  | @ -42,15 +68,12 @@ struct PoolConfig { | |||
| }; | ||||
| 
 | ||||
| /// Calculate the pool for the mesh for SLA printing
 | ||||
| void create_base_pool(const ExPolygons& base_plate, | ||||
| void create_base_pool(const Polygons& base_plate, | ||||
|                       TriangleMesh& output_mesh, | ||||
|                       const ExPolygons& holes, | ||||
|                       const PoolConfig& = PoolConfig()); | ||||
| 
 | ||||
| /// TODO: Currently the base plate of the pool will have half the height of the
 | ||||
| /// whole pool. So the carved out space has also half the height. This is not
 | ||||
| /// a particularly elegant solution, the thickness should be exactly
 | ||||
| /// min_wall_thickness and it should be corrected in the future. This method
 | ||||
| /// will return the correct value for further processing.
 | ||||
| /// Returns the elevation needed for compensating the pad.
 | ||||
| inline double get_pad_elevation(const PoolConfig& cfg) { | ||||
|     return cfg.min_wall_thickness_mm; | ||||
| } | ||||
|  |  | |||
|  | @ -60,7 +60,7 @@ class EigenMesh3D { | |||
| 
 | ||||
|     Eigen::MatrixXd m_V; | ||||
|     Eigen::MatrixXi m_F; | ||||
|     double m_ground_level = 0; | ||||
|     double m_ground_level = 0, m_gnd_offset = 0; | ||||
| 
 | ||||
|     std::unique_ptr<AABBImpl> m_aabb; | ||||
| public: | ||||
|  | @ -71,7 +71,9 @@ public: | |||
| 
 | ||||
|     ~EigenMesh3D(); | ||||
| 
 | ||||
|     inline double ground_level() const { return m_ground_level; } | ||||
|     inline double ground_level() const { return m_ground_level + m_gnd_offset; } | ||||
|     inline void ground_level_offset(double o) { m_gnd_offset = o; } | ||||
|     inline double ground_level_offset() const { return m_gnd_offset; } | ||||
| 
 | ||||
|     inline const Eigen::MatrixXd& V() const { return m_V; } | ||||
|     inline const Eigen::MatrixXi& F() const { return m_F; } | ||||
|  | @ -149,6 +151,12 @@ public: | |||
| #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ | ||||
| 
 | ||||
|     double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; | ||||
|     inline double squared_distance(const Vec3d &p) const | ||||
|     { | ||||
|         int   i; | ||||
|         Vec3d c; | ||||
|         return squared_distance(p, i, c); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,13 +7,15 @@ | |||
| 
 | ||||
| #include <Eigen/Geometry> | ||||
| 
 | ||||
| #include <libslic3r/BoundingBox.hpp> | ||||
| 
 | ||||
| namespace Slic3r { | ||||
| namespace sla { | ||||
| 
 | ||||
| typedef Eigen::Matrix<double,   3, 1, Eigen::DontAlign> Vec3d; | ||||
| using SpatElement = std::pair<Vec3d, unsigned>; | ||||
| using PointIndexEl = std::pair<Vec3d, unsigned>; | ||||
| 
 | ||||
| class SpatIndex { | ||||
| class PointIndex { | ||||
|     class Impl; | ||||
| 
 | ||||
|     // We use Pimpl because it takes a long time to compile boost headers which
 | ||||
|  | @ -21,30 +23,67 @@ class SpatIndex { | |||
|     std::unique_ptr<Impl> m_impl; | ||||
| public: | ||||
| 
 | ||||
|     SpatIndex(); | ||||
|     ~SpatIndex(); | ||||
|     PointIndex(); | ||||
|     ~PointIndex(); | ||||
| 
 | ||||
|     SpatIndex(const SpatIndex&); | ||||
|     SpatIndex(SpatIndex&&); | ||||
|     SpatIndex& operator=(const SpatIndex&); | ||||
|     SpatIndex& operator=(SpatIndex&&); | ||||
|     PointIndex(const PointIndex&); | ||||
|     PointIndex(PointIndex&&); | ||||
|     PointIndex& operator=(const PointIndex&); | ||||
|     PointIndex& operator=(PointIndex&&); | ||||
| 
 | ||||
|     void insert(const SpatElement&); | ||||
|     bool remove(const SpatElement&); | ||||
|     void insert(const PointIndexEl&); | ||||
|     bool remove(const PointIndexEl&); | ||||
| 
 | ||||
|     inline void insert(const Vec3d& v, unsigned idx) | ||||
|     { | ||||
|         insert(std::make_pair(v, unsigned(idx))); | ||||
|     } | ||||
| 
 | ||||
|     std::vector<SpatElement> query(std::function<bool(const SpatElement&)>); | ||||
|     std::vector<SpatElement> nearest(const Vec3d&, unsigned k); | ||||
|     std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>); | ||||
|     std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k); | ||||
| 
 | ||||
|     // For testing
 | ||||
|     size_t size() const; | ||||
|     bool empty() const { return size() == 0; } | ||||
| 
 | ||||
|     void foreach(std::function<void(const SpatElement& el)> fn); | ||||
|     void foreach(std::function<void(const PointIndexEl& el)> fn); | ||||
| }; | ||||
| 
 | ||||
| using BoxIndexEl = std::pair<Slic3r::BoundingBox, unsigned>; | ||||
| 
 | ||||
| class BoxIndex { | ||||
|     class Impl; | ||||
|      | ||||
|     // We use Pimpl because it takes a long time to compile boost headers which
 | ||||
|     // is the engine of this class. We include it only in the cpp file.
 | ||||
|     std::unique_ptr<Impl> m_impl; | ||||
| public: | ||||
|      | ||||
|     BoxIndex(); | ||||
|     ~BoxIndex(); | ||||
|      | ||||
|     BoxIndex(const BoxIndex&); | ||||
|     BoxIndex(BoxIndex&&); | ||||
|     BoxIndex& operator=(const BoxIndex&); | ||||
|     BoxIndex& operator=(BoxIndex&&); | ||||
|      | ||||
|     void insert(const BoxIndexEl&); | ||||
|     inline void insert(const BoundingBox& bb, unsigned idx) | ||||
|     { | ||||
|         insert(std::make_pair(bb, unsigned(idx))); | ||||
|     } | ||||
|      | ||||
|     bool remove(const BoxIndexEl&); | ||||
| 
 | ||||
|     enum QueryType { qtIntersects, qtWithin }; | ||||
| 
 | ||||
|     std::vector<BoxIndexEl> query(const BoundingBox&, QueryType qt); | ||||
|      | ||||
|     // For testing
 | ||||
|     size_t size() const; | ||||
|     bool empty() const { return size() == 0; } | ||||
|      | ||||
|     void foreach(std::function<void(const BoxIndexEl& el)> fn); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -9,10 +9,12 @@ | |||
| #include "SLASpatIndex.hpp" | ||||
| #include "SLABasePool.hpp" | ||||
| 
 | ||||
| #include <libslic3r/MTUtils.hpp> | ||||
| #include <libslic3r/ClipperUtils.hpp> | ||||
| #include <libslic3r/Model.hpp> | ||||
| 
 | ||||
| #include <libnest2d/optimizers/nlopt/genetic.hpp> | ||||
| #include <libnest2d/optimizers/nlopt/subplex.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <tbb/parallel_for.h> | ||||
| #include <libslic3r/I18N.hpp> | ||||
|  | @ -413,7 +415,7 @@ struct Pillar { | |||
|         assert(steps > 0); | ||||
| 
 | ||||
|         height = jp(Z) - endp(Z); | ||||
|         if(height > 0) { // Endpoint is below the starting point
 | ||||
|         if(height > EPSILON) { // Endpoint is below the starting point
 | ||||
| 
 | ||||
|             // We just create a bridge geometry with the pillar parameters and
 | ||||
|             // move the data.
 | ||||
|  | @ -528,6 +530,7 @@ struct CompactBridge { | |||
|                   const Vec3d& ep, | ||||
|                   const Vec3d& n, | ||||
|                   double r, | ||||
|                   bool endball = true, | ||||
|                   size_t steps = 45) | ||||
|     { | ||||
|         Vec3d startp = sp + r * n; | ||||
|  | @ -542,11 +545,13 @@ struct CompactBridge { | |||
|         auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); | ||||
|         for(auto& p : upperball.points) p += startp; | ||||
|          | ||||
|         auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); | ||||
|         for(auto& p : lowerball.points) p += endp; | ||||
|         if(endball) { | ||||
|             auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); | ||||
|             for(auto& p : lowerball.points) p += endp; | ||||
|             mesh.merge(lowerball); | ||||
|         } | ||||
|          | ||||
|         mesh.merge(upperball); | ||||
|         mesh.merge(lowerball); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | @ -556,28 +561,111 @@ struct Pad { | |||
|     PoolConfig cfg; | ||||
|     double zlevel = 0; | ||||
| 
 | ||||
|     Pad() {} | ||||
|     Pad() = default; | ||||
| 
 | ||||
|     Pad(const TriangleMesh& object_support_mesh, | ||||
|         const ExPolygons& baseplate, | ||||
|     Pad(const TriangleMesh& support_mesh, | ||||
|         const ExPolygons& modelbase, | ||||
|         double ground_level, | ||||
|         const PoolConfig& pcfg) : | ||||
|         cfg(pcfg), | ||||
|         zlevel(ground_level +  | ||||
|                (sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) ) | ||||
|                sla::get_pad_fullheight(pcfg) - | ||||
|                sla::get_pad_elevation(pcfg)) | ||||
|     { | ||||
|         ExPolygons basep; | ||||
|         cfg.throw_on_cancel(); | ||||
|         Polygons basep; | ||||
|         auto &thr = cfg.throw_on_cancel; | ||||
|          | ||||
|         // The 0.1f is the layer height with which the mesh is sampled and then
 | ||||
|         // the layers are unified into one vector of polygons.
 | ||||
|         base_plate(object_support_mesh, basep, | ||||
|                    float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), | ||||
|                    0.1f, pcfg.throw_on_cancel); | ||||
|         thr(); | ||||
|          | ||||
|         for(auto& bp : baseplate) basep.emplace_back(bp); | ||||
|         // Get a sample for the pad from the support mesh
 | ||||
|         { | ||||
|             ExPolygons platetmp; | ||||
| 
 | ||||
|             float zstart = float(zlevel); | ||||
|             float zend   = zstart + float(get_pad_fullheight(pcfg) + EPSILON); | ||||
| 
 | ||||
|             base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); | ||||
| 
 | ||||
|             // We don't need no... holes control...
 | ||||
|             for (const ExPolygon &bp : platetmp) | ||||
|                 basep.emplace_back(std::move(bp.contour)); | ||||
|         } | ||||
|          | ||||
|         if(pcfg.embed_object) { | ||||
|              | ||||
|             // If the zero elevation mode is ON, we need to process the model
 | ||||
|             // base silhouette. Create the offsetted version and punch the
 | ||||
|             // breaksticks across its perimeter.
 | ||||
|              | ||||
|             ExPolygons modelbase_offs = modelbase; | ||||
|              | ||||
|             if (pcfg.embed_object.object_gap_mm > 0.0) | ||||
|                 modelbase_offs | ||||
|                     = offset_ex(modelbase_offs, | ||||
|                                 float(scaled(pcfg.embed_object.object_gap_mm))); | ||||
|              | ||||
|             // Create a spatial index of the support silhouette polygons.
 | ||||
|             // This will be used to check for intersections with the model
 | ||||
|             // silhouette polygons. If there is no intersection, then a certain
 | ||||
|             // part of the pad is redundant as it does not host any supports.
 | ||||
|             BoxIndex bindex; | ||||
|             { | ||||
|                 unsigned idx = 0; | ||||
|                 for(auto &bp : basep) { | ||||
|                     auto bb = bp.bounding_box(); | ||||
|                     bb.offset(float(scaled(pcfg.min_wall_thickness_mm))); | ||||
|                     bindex.insert(bb, idx++); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             // Punching the breaksticks across the offsetted polygon perimeters
 | ||||
|             ExPolygons pad_stickholes; pad_stickholes.reserve(modelbase.size()); | ||||
|             for(auto& poly : modelbase_offs) { | ||||
|                  | ||||
|                 std::vector<BoxIndexEl> qres = | ||||
|                     bindex.query(poly.contour.bounding_box(), | ||||
|                                  BoxIndex::qtIntersects); | ||||
|                      | ||||
|                 if (!qres.empty()) { | ||||
|                      | ||||
|                     // The model silhouette polygon 'poly' HAS an intersection
 | ||||
|                     // with the support silhouettes. Include this polygon
 | ||||
|                     // in the pad holes with the breaksticks and merge the
 | ||||
|                     // original (offsetted) version with the rest of the pad
 | ||||
|                     // base plate.
 | ||||
|                      | ||||
|                     basep.emplace_back(poly.contour); | ||||
|                      | ||||
|                     // The holes of 'poly' will become positive parts of the
 | ||||
|                     // pad, so they has to be checked for intersections as well
 | ||||
|                     // and erased if there is no intersection with the supports
 | ||||
|                     auto it = poly.holes.begin(); | ||||
|                     while(it != poly.holes.end()) { | ||||
|                         if (bindex.query(it->bounding_box(), | ||||
|                                          BoxIndex::qtIntersects).empty()) | ||||
|                             it = poly.holes.erase(it); | ||||
|                         else | ||||
|                             ++it; | ||||
|                     } | ||||
|                      | ||||
|                     // Punch the breaksticks
 | ||||
|                     sla::breakstick_holes( | ||||
|                         poly, | ||||
|                         pcfg.embed_object.object_gap_mm,   // padding
 | ||||
|                         pcfg.embed_object.stick_stride_mm, | ||||
|                         pcfg.embed_object.stick_width_mm, | ||||
|                         pcfg.embed_object.stick_penetration_mm); | ||||
|                      | ||||
|                     pad_stickholes.emplace_back(poly); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             create_base_pool(basep, tmesh, pad_stickholes, cfg); | ||||
|         } else { | ||||
|             for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); | ||||
|             create_base_pool(basep, tmesh, {}, cfg); | ||||
|         } | ||||
| 
 | ||||
|         create_base_pool(basep, tmesh, cfg); | ||||
|         tmesh.translate(0, 0, float(zlevel)); | ||||
|     } | ||||
| 
 | ||||
|  | @ -603,7 +691,7 @@ inline Vec2d to_vec2(const Vec3d& v3) { | |||
|     return {v3(X), v3(Y)}; | ||||
| } | ||||
| 
 | ||||
| bool operator==(const SpatElement& e1, const SpatElement& e2) { | ||||
| bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { | ||||
|     return e1.second == e2.second; | ||||
| } | ||||
| 
 | ||||
|  | @ -620,7 +708,7 @@ ClusteredPoints cluster(const PointSet& points, | |||
| ClusteredPoints cluster( | ||||
|         const std::vector<unsigned>& indices, | ||||
|         std::function<Vec3d(unsigned)> pointfn, | ||||
|         std::function<bool(const SpatElement&, const SpatElement&)> predicate, | ||||
|         std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|         unsigned max_points); | ||||
| 
 | ||||
| // This class will hold the support tree meshes with some additional bookkeeping
 | ||||
|  | @ -763,9 +851,9 @@ public: | |||
|     } | ||||
| 
 | ||||
|     const Pad& create_pad(const TriangleMesh& object_supports, | ||||
|                           const ExPolygons& baseplate, | ||||
|                           const ExPolygons& modelbase, | ||||
|                           const PoolConfig& cfg) { | ||||
|         m_pad = Pad(object_supports, baseplate, ground_level, cfg); | ||||
|         m_pad = Pad(object_supports, modelbase, ground_level, cfg); | ||||
|         return m_pad; | ||||
|     } | ||||
| 
 | ||||
|  | @ -808,7 +896,6 @@ public: | |||
|             merged.merge(bs.mesh); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         if(m_ctl.stopcondition()) { | ||||
|             // In case of failure we have to return an empty mesh
 | ||||
|             meshcache = TriangleMesh(); | ||||
|  | @ -819,7 +906,7 @@ public: | |||
| 
 | ||||
|         // The mesh will be passed by const-pointer to TriangleMeshSlicer,
 | ||||
|         // which will need this.
 | ||||
|         meshcache.require_shared_vertices(); | ||||
|         if (!meshcache.empty()) meshcache.require_shared_vertices(); | ||||
| 
 | ||||
|         // TODO: Is this necessary?
 | ||||
|         //meshcache.repair();
 | ||||
|  | @ -947,7 +1034,7 @@ class SLASupportTree::Algorithm { | |||
|     ThrowOnCancel m_thr; | ||||
| 
 | ||||
|     // A spatial index to easily find strong pillars to connect to.
 | ||||
|     SpatIndex m_pillar_index; | ||||
|     PointIndex m_pillar_index; | ||||
| 
 | ||||
|     inline double ray_mesh_intersect(const Vec3d& s, | ||||
|                                      const Vec3d& dir) | ||||
|  | @ -1149,7 +1236,7 @@ class SLASupportTree::Algorithm { | |||
|             auto hr = m.query_ray_hit(p + sd*dir, dir); | ||||
| 
 | ||||
|             if(ins_check && hr.is_inside()) { | ||||
|                 if(hr.distance() > r + sd) hits[i] = HitResult(0.0); | ||||
|                 if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); | ||||
|                 else { | ||||
|                     // re-cast the ray from the outside of the object
 | ||||
|                     auto hr2 = | ||||
|  | @ -1265,8 +1352,11 @@ class SLASupportTree::Algorithm { | |||
|     // For connecting a head to a nearby pillar.
 | ||||
|     bool connect_to_nearpillar(const Head& head, long nearpillar_id) { | ||||
|          | ||||
|         auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; | ||||
|         if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; | ||||
|         auto nearpillar = [this, nearpillar_id]() { | ||||
|             return m_result.pillar(nearpillar_id); | ||||
|         }; | ||||
|          | ||||
|         if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; | ||||
| 
 | ||||
|         Vec3d headjp = head.junction_point(); | ||||
|         Vec3d nearjp_u = nearpillar().startpoint(); | ||||
|  | @ -1337,7 +1427,7 @@ class SLASupportTree::Algorithm { | |||
|     } | ||||
| 
 | ||||
|     bool search_pillar_and_connect(const Head& head) { | ||||
|         SpatIndex spindex = m_pillar_index; | ||||
|         PointIndex spindex = m_pillar_index; | ||||
| 
 | ||||
|         long nearest_id = -1; | ||||
| 
 | ||||
|  | @ -1370,6 +1460,120 @@ class SLASupportTree::Algorithm { | |||
|         return nearest_id >= 0; | ||||
|     } | ||||
|      | ||||
|     // This is a proxy function for pillar creation which will mind the gap
 | ||||
|     // between the pad and the model bottom in zero elevation mode.
 | ||||
|     void create_ground_pillar(const Vec3d &jp, | ||||
|                               const Vec3d &sourcedir, | ||||
|                               double       radius, | ||||
|                               int          head_id = -1) | ||||
|     { | ||||
|         // People were killed for this number (seriously)
 | ||||
|         static const double SQR2 = std::sqrt(2.0); | ||||
|         static const Vec3d  DOWN = {0.0, 0.0, -1.0}; | ||||
| 
 | ||||
|         double gndlvl       = m_result.ground_level; | ||||
|         Vec3d  endp         = {jp(X), jp(Y), gndlvl}; | ||||
|         double sd           = m_cfg.pillar_base_safety_distance_mm; | ||||
|         int    pillar_id    = -1; | ||||
|         double min_dist     = sd + m_cfg.base_radius_mm + EPSILON; | ||||
|         double dist         = 0; | ||||
|         bool   can_add_base = true; | ||||
|         bool   normal_mode  = true; | ||||
| 
 | ||||
|         if (m_cfg.object_elevation_mm < EPSILON | ||||
|             && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { | ||||
|             // Get the distance from the mesh. This can be later optimized
 | ||||
|             // to get the distance in 2D plane because we are dealing with
 | ||||
|             // the ground level only.
 | ||||
| 
 | ||||
|             normal_mode     = false; | ||||
|             double mv       = min_dist - dist; | ||||
|             double azimuth  = std::atan2(sourcedir(Y), sourcedir(X)); | ||||
|             double sinpolar = std::sin(PI - m_cfg.bridge_slope); | ||||
|             double cospolar = std::cos(PI - m_cfg.bridge_slope); | ||||
|             double cosazm   = std::cos(azimuth); | ||||
|             double sinazm   = std::sin(azimuth); | ||||
| 
 | ||||
|             auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) | ||||
|                            .normalized(); | ||||
| 
 | ||||
|             using namespace libnest2d::opt; | ||||
|             StopCriteria scr; | ||||
|             scr.stop_score = min_dist; | ||||
|             SubplexOptimizer solver(scr); | ||||
| 
 | ||||
|             auto result = solver.optimize_max( | ||||
|                 [this, dir, jp, gndlvl](double mv) { | ||||
|                     Vec3d endp = jp + SQR2 * mv * dir; | ||||
|                     endp(Z)    = gndlvl; | ||||
|                     return std::sqrt(m_mesh.squared_distance(endp)); | ||||
|                 }, | ||||
|                 initvals(mv), bound(0.0, 2 * min_dist)); | ||||
| 
 | ||||
|             mv           = std::get<0>(result.optimum); | ||||
|             endp         = jp + SQR2 * mv * dir; | ||||
|             Vec3d pgnd   = {endp(X), endp(Y), gndlvl}; | ||||
|             can_add_base = result.score > min_dist; | ||||
|              | ||||
|             double gnd_offs = m_mesh.ground_level_offset(); | ||||
|             auto abort_in_shame = | ||||
|                 [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() | ||||
|             { | ||||
|                 normal_mode  = true; | ||||
|                 can_add_base = false;   // Nothing left to do, hope for the best
 | ||||
|                 endp         = {jp(X), jp(Y), gndlvl - gnd_offs }; | ||||
|             }; | ||||
| 
 | ||||
|             // We have to check if the bridge is feasible.
 | ||||
|             if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) | ||||
|                 abort_in_shame(); | ||||
|             else { | ||||
|                 // If the new endpoint is below ground, do not make a pillar
 | ||||
|                 if (endp(Z) < gndlvl) | ||||
|                     endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
 | ||||
|                 else { | ||||
|                      | ||||
|                     auto hit = bridge_mesh_intersect(endp, DOWN, radius); | ||||
|                     if (!std::isinf(hit.distance())) abort_in_shame(); | ||||
| 
 | ||||
|                     Pillar &plr = m_result.add_pillar(endp, pgnd, radius); | ||||
| 
 | ||||
|                     if (can_add_base) | ||||
|                         plr.add_base(m_cfg.base_height_mm, | ||||
|                                      m_cfg.base_radius_mm); | ||||
| 
 | ||||
|                     pillar_id = plr.id; | ||||
|                 } | ||||
| 
 | ||||
|                 m_result.add_bridge(jp, endp, radius); | ||||
|                 m_result.add_junction(endp, radius); | ||||
| 
 | ||||
|                 // Add a degenerated pillar and the bridge.
 | ||||
|                 // The degenerate pillar will have zero length and it will
 | ||||
|                 // prevent from queries of head_pillar() to have non-existing
 | ||||
|                 // pillar when the head should have one.
 | ||||
|                 if (head_id >= 0) | ||||
|                     m_result.add_pillar(unsigned(head_id), jp, radius); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         if (normal_mode) { | ||||
|             Pillar &plr = head_id >= 0 | ||||
|                               ? m_result.add_pillar(unsigned(head_id), | ||||
|                                                     endp, | ||||
|                                                     radius) | ||||
|                               : m_result.add_pillar(jp, endp, radius); | ||||
| 
 | ||||
|             if (can_add_base) | ||||
|                 plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); | ||||
| 
 | ||||
|             pillar_id = plr.id; | ||||
|         }  | ||||
|              | ||||
|         if(pillar_id >= 0) // Save the pillar endpoint in the spatial index
 | ||||
|             m_pillar_index.insert(endp, pillar_id); | ||||
|     } | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     Algorithm(const SupportConfig& config, | ||||
|  | @ -1447,9 +1651,9 @@ public: | |||
|             // (Quaternion::FromTwoVectors) and apply the rotation to the
 | ||||
|             // arrow head.
 | ||||
| 
 | ||||
|             double z = n(2); | ||||
|             double r = 1.0;     // for normalized vector
 | ||||
|             double polar = std::acos(z / r); | ||||
|             double z       = n(2); | ||||
|             double r       = 1.0; // for normalized vector
 | ||||
|             double polar   = std::acos(z / r); | ||||
|             double azimuth = std::atan2(n(1), n(0)); | ||||
| 
 | ||||
|             // skip if the tilt is not sane
 | ||||
|  | @ -1473,14 +1677,14 @@ public: | |||
|                                 std::cos(polar)).normalized(); | ||||
| 
 | ||||
|                 // check available distance
 | ||||
|                 double t = pinhead_mesh_intersect( | ||||
|                                   hp, // touching point
 | ||||
|                                   nn, // normal
 | ||||
|                                   pin_r, | ||||
|                                   m_cfg.head_back_radius_mm, | ||||
|                                   w); | ||||
|                 EigenMesh3D::hit_result t | ||||
|                     = pinhead_mesh_intersect(hp, // touching point
 | ||||
|                                              nn, // normal
 | ||||
|                                              pin_r, | ||||
|                                              m_cfg.head_back_radius_mm, | ||||
|                                              w); | ||||
| 
 | ||||
|                 if(t <= w) { | ||||
|                 if(t.distance() <= w) { | ||||
| 
 | ||||
|                     // Let's try to optimize this angle, there might be a
 | ||||
|                     // viable normal that doesn't collide with the model
 | ||||
|  | @ -1523,12 +1727,17 @@ public: | |||
|                 // save the verified and corrected normal
 | ||||
|                 m_support_nmls.row(fidx) = nn; | ||||
| 
 | ||||
|                 if(t > w) { | ||||
|                     // mark the point for needing a head.
 | ||||
|                     m_iheads.emplace_back(fidx); | ||||
|                 } else if( polar >= 3*PI/4 ) { | ||||
|                     // Headless supports do not tilt like the headed ones so
 | ||||
|                     // the normal should point almost to the ground.
 | ||||
|                 if (t.distance() > w) { | ||||
|                     // Check distance from ground, we might have zero elevation.
 | ||||
|                     if (hp(Z) + w * nn(Z) < m_result.ground_level) { | ||||
|                         m_iheadless.emplace_back(fidx); | ||||
|                     } else { | ||||
|                         // mark the point for needing a head.
 | ||||
|                         m_iheads.emplace_back(fidx); | ||||
|                     } | ||||
|                 } else if (polar >= 3 * PI / 4) { | ||||
|                     // Headless supports do not tilt like the headed ones
 | ||||
|                     // so the normal should point almost to the ground.
 | ||||
|                     m_iheadless.emplace_back(fidx); | ||||
|                 } | ||||
|             } | ||||
|  | @ -1594,16 +1803,22 @@ public: | |||
|         // from each other in the XY plane to not cross their pillar bases
 | ||||
|         // These clusters of support points will join in one pillar,
 | ||||
|         // possibly in their centroid support point.
 | ||||
|          | ||||
|         auto pointfn = [this](unsigned i) { | ||||
|             return m_result.head(i).junction_point(); | ||||
|         }; | ||||
|         auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { | ||||
| 
 | ||||
|         auto predicate = [this](const PointIndexEl &e1, | ||||
|                                 const PointIndexEl &e2) { | ||||
|             double d2d = distance(to_2d(e1.first), to_2d(e2.first)); | ||||
|             double d3d = distance(e1.first, e2.first); | ||||
|             return d2d < 2 * m_cfg.base_radius_mm && | ||||
|                    d3d < m_cfg.max_bridge_length_mm; | ||||
|             return d2d < 2 * m_cfg.base_radius_mm | ||||
|                    && d3d < m_cfg.max_bridge_length_mm; | ||||
|         }; | ||||
|         m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, | ||||
| 
 | ||||
|         m_pillar_clusters = cluster(ground_head_indices, | ||||
|                                     pointfn, | ||||
|                                     predicate, | ||||
|                                     m_cfg.max_bridges_on_pillar); | ||||
|     } | ||||
| 
 | ||||
|  | @ -1615,7 +1830,7 @@ public: | |||
|     void routing_to_ground() | ||||
|     { | ||||
|         const double pradius = m_cfg.head_back_radius_mm; | ||||
|         const double gndlvl = m_result.ground_level; | ||||
|         // const double gndlvl = m_result.ground_level;
 | ||||
| 
 | ||||
|         ClusterEl cl_centroids; | ||||
|         cl_centroids.reserve(m_pillar_clusters.size()); | ||||
|  | @ -1648,13 +1863,8 @@ public: | |||
| 
 | ||||
|             Head& h = m_result.head(hid); | ||||
|             h.transform(); | ||||
|             Vec3d p = h.junction_point(); p(Z) = gndlvl; | ||||
|             auto& plr = m_result.add_pillar(hid, p, h.r_back_mm) | ||||
|                                 .add_base(m_cfg.base_height_mm, | ||||
|                                           m_cfg.base_radius_mm); | ||||
| 
 | ||||
|             // Save the pillar endpoint and the pillar id in the spatial index
 | ||||
|             m_pillar_index.insert(plr.endpoint(), unsigned(plr.id)); | ||||
|             create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); | ||||
|         } | ||||
| 
 | ||||
|         // now we will go through the clusters ones again and connect the
 | ||||
|  | @ -1681,15 +1891,12 @@ public: | |||
|                    !search_pillar_and_connect(sidehead)) | ||||
|                 { | ||||
|                     Vec3d pstart = sidehead.junction_point(); | ||||
|                     Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; | ||||
|                     //Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl};
 | ||||
|                     // Could not find a pillar, create one
 | ||||
|                     auto& pillar = m_result.add_pillar(unsigned(sidehead.id), | ||||
|                                                        pend, pradius) | ||||
|                                            .add_base(m_cfg.base_height_mm, | ||||
|                                                      m_cfg.base_radius_mm); | ||||
| 
 | ||||
|                     // connects to ground, eligible for bridging
 | ||||
|                     m_pillar_index.insert(pend, unsigned(pillar.id)); | ||||
|                     create_ground_pillar(pstart, | ||||
|                                          sidehead.dir, | ||||
|                                          pradius, | ||||
|                                          sidehead.id); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -1718,12 +1925,7 @@ public: | |||
|             m_result.add_bridge(hjp, endp, head.r_back_mm); | ||||
|             m_result.add_junction(endp, head.r_back_mm); | ||||
| 
 | ||||
|             auto groundp = endp; | ||||
|             groundp(Z) = m_result.ground_level; | ||||
|             auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm) | ||||
|                                       .add_base(m_cfg.base_height_mm, | ||||
|                                                 m_cfg.base_radius_mm); | ||||
|             m_pillar_index.insert(groundp, unsigned(newpillar.id)); | ||||
|             this->create_ground_pillar(endp, dir, head.r_back_mm); | ||||
|         }; | ||||
| 
 | ||||
|         std::vector<unsigned> modelpillars; | ||||
|  | @ -1884,6 +2086,28 @@ public: | |||
|         } | ||||
|     } | ||||
|      | ||||
|     // Helper function for interconnect_pillars where pairs of already connected
 | ||||
|     // pillars should be checked for not to be processed again. This can be done
 | ||||
|     // in O(log) or even constant time with a set or an unordered set of hash
 | ||||
|     // values uniquely representing a pair of integers. The order of numbers
 | ||||
|     // within the pair should not matter, it has the same unique hash.
 | ||||
|     template<class I> static I pairhash(I a, I b) | ||||
|     { | ||||
|         using std::ceil; using std::log2; using std::max; using std::min; | ||||
|          | ||||
|         static_assert(std::is_integral<I>::value, | ||||
|                       "This function works only for integral types."); | ||||
| 
 | ||||
|         I g = min(a, b), l = max(a, b); | ||||
|          | ||||
|         auto bits_g = g ? int(ceil(log2(g))) : 0; | ||||
| 
 | ||||
|         // Assume the hash will fit into the output variable
 | ||||
|         assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); | ||||
|          | ||||
|         return (l << bits_g) + g; | ||||
|     } | ||||
| 
 | ||||
|     void interconnect_pillars() { | ||||
|         // Now comes the algorithm that connects pillars with each other.
 | ||||
|         // Ideally every pillar should be connected with at least one of its
 | ||||
|  | @ -1901,44 +2125,50 @@ public: | |||
| 
 | ||||
|         std::set<unsigned long> pairs; | ||||
|          | ||||
|         // A function to connect one pillar with its neighbors. THe number of
 | ||||
|         // neighbors is given in the configuration. This function if called
 | ||||
|         // for every pillar in the pillar index. A pair of pillar will not
 | ||||
|         // be connected multiple times this is ensured by the 'pairs' set which
 | ||||
|         // remembers the processed pillar pairs
 | ||||
|         auto cascadefn = | ||||
|                 [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) | ||||
|                 [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) | ||||
|         { | ||||
|             Vec3d qp = el.first; | ||||
|             Vec3d qp = el.first;    // endpoint of the pillar
 | ||||
| 
 | ||||
|             const Pillar& pillar = m_result.pillar(el.second); | ||||
|             const Pillar& pillar = m_result.pillar(el.second); // actual pillar
 | ||||
|              | ||||
|             // Get the max number of neighbors a pillar should connect to
 | ||||
|             unsigned neighbors = m_cfg.pillar_cascade_neighbors; | ||||
| 
 | ||||
|             // connections are enough for one pillar
 | ||||
|             // connections are already enough for the pillar
 | ||||
|             if(pillar.links >= neighbors) return; | ||||
| 
 | ||||
|             // Query all remaining points within reach
 | ||||
|             auto qres = m_pillar_index.query([qp, d](const SpatElement& e){ | ||||
|             auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ | ||||
|                 return distance(e.first, qp) < d; | ||||
|             }); | ||||
| 
 | ||||
|             // sort the result by distance (have to check if this is needed)
 | ||||
|             std::sort(qres.begin(), qres.end(), | ||||
|                       [qp](const SpatElement& e1, const SpatElement& e2){ | ||||
|                       [qp](const PointIndexEl& e1, const PointIndexEl& e2){ | ||||
|                 return distance(e1.first, qp) < distance(e2.first, qp); | ||||
|             }); | ||||
| 
 | ||||
|             for(auto& re : qres) { | ||||
|             for(auto& re : qres) { // process the queried neighbors
 | ||||
| 
 | ||||
|                 if(re.second == el.second) continue; | ||||
|                 if(re.second == el.second) continue; // Skip self
 | ||||
| 
 | ||||
|                 auto a = el.second, b = re.second; | ||||
| 
 | ||||
|                 // I hope that the area of a square is never equal to its
 | ||||
|                 // circumference
 | ||||
|                 auto hashval = 2 * (a + b) + a * b; | ||||
|                 // Get unique hash for the given pair (order doesn't matter)
 | ||||
|                 auto hashval = pairhash(a, b); | ||||
|                  | ||||
|                 // Search for the pair amongst the remembered pairs
 | ||||
|                 if(pairs.find(hashval) != pairs.end()) continue; | ||||
| 
 | ||||
|                 const Pillar& neighborpillar = m_result.pillars()[re.second]; | ||||
| 
 | ||||
|                 // this neighbor is occupied
 | ||||
|                 // this neighbor is occupied, skip
 | ||||
|                 if(neighborpillar.links >= neighbors) continue; | ||||
| 
 | ||||
|                 if(interconnect(pillar, neighborpillar)) { | ||||
|  | @ -1961,46 +2191,78 @@ public: | |||
|             } | ||||
|         }; | ||||
|          | ||||
|         // Run the cascade for the pillars in the index
 | ||||
|         m_pillar_index.foreach(cascadefn); | ||||
|         | ||||
|         // We would be done here if we could allow some pillars to not be
 | ||||
|         // connected with any neighbors. But this might leave the support tree
 | ||||
|         // unprintable.
 | ||||
|         //
 | ||||
|         // The current solution is to insert additional pillars next to these
 | ||||
|         // lonely pillars. One or even two additional pillar might get inserted
 | ||||
|         // depending on the length of the lonely pillar.
 | ||||
|          | ||||
|         size_t pillarcount = m_result.pillars().size(); | ||||
|          | ||||
|         // Again, go through all pillars, this time in the whole support tree
 | ||||
|         // not just the index.
 | ||||
|         for(size_t pid = 0; pid < pillarcount; pid++) { | ||||
|             auto pillar = [this, pid]() { return m_result.pillar(pid); }; | ||||
|             | ||||
|             // Decide how many additional pillars will be needed:
 | ||||
|              | ||||
|             unsigned needpillars = 0; | ||||
|             if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; | ||||
|             else if(pillar().links < 2 && pillar().height > H2) { | ||||
|             if (pillar().bridges > m_cfg.max_bridges_on_pillar) | ||||
|                 needpillars = 3; | ||||
|             else if (pillar().links < 2 && pillar().height > H2) { | ||||
|                 // Not enough neighbors to support this pillar
 | ||||
|                 needpillars = 2 - pillar().links; | ||||
|             } | ||||
|             else if(pillar().links < 1 && pillar().height > H1) { | ||||
|             } else if (pillar().links < 1 && pillar().height > H1) { | ||||
|                 // No neighbors could be found and the pillar is too long.
 | ||||
|                 needpillars = 1; | ||||
|             } | ||||
| 
 | ||||
|             // Search for new pillar locations
 | ||||
|             bool found = false; | ||||
|             double alpha = 0; // goes to 2Pi
 | ||||
|             double r = 2 * m_cfg.base_radius_mm; | ||||
|             Vec3d pillarsp = pillar().startpoint(); | ||||
|             // Search for new pillar locations:
 | ||||
| 
 | ||||
|             bool   found    = false; | ||||
|             double alpha    = 0; // goes to 2Pi
 | ||||
|             double r        = 2 * m_cfg.base_radius_mm; | ||||
|             Vec3d  pillarsp = pillar().startpoint(); | ||||
| 
 | ||||
|             // temp value for starting point detection
 | ||||
|             Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); | ||||
|             std::vector<bool> tv(needpillars, false); | ||||
|             std::vector<Vec3d> spts(needpillars); | ||||
| 
 | ||||
|             // A vector of bool for placement feasbility
 | ||||
|             std::vector<bool>  canplace(needpillars, false); | ||||
|             std::vector<Vec3d> spts(needpillars); // vector of starting points
 | ||||
| 
 | ||||
|             double gnd      = m_result.ground_level; | ||||
|             double min_dist = m_cfg.pillar_base_safety_distance_mm + | ||||
|                               m_cfg.base_radius_mm + EPSILON; | ||||
|              | ||||
|             while(!found && alpha < 2*PI) { | ||||
| 
 | ||||
|                 for(unsigned n = 0; n < needpillars; n++) { | ||||
|                     double a = alpha + n * PI/3; | ||||
|                     Vec3d s = sp; | ||||
|                 for (unsigned n = 0; | ||||
|                      n < needpillars && (!n || canplace[n - 1]); | ||||
|                      n++) | ||||
|                 { | ||||
|                     double a = alpha + n * PI / 3; | ||||
|                     Vec3d  s = sp; | ||||
|                     s(X) += std::cos(a) * r; | ||||
|                     s(Y) += std::sin(a) * r; | ||||
|                     spts[n] = s; | ||||
|                      | ||||
|                     // Check the path vertically down                    
 | ||||
|                     auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); | ||||
|                     tv[n] = std::isinf(hr.distance()); | ||||
|                     Vec3d gndsp{s(X), s(Y), gnd}; | ||||
|                      | ||||
|                     // If the path is clear, check for pillar base collisions
 | ||||
|                     canplace[n] = std::isinf(hr.distance()) && | ||||
|                                   std::sqrt(m_mesh.squared_distance(gndsp)) > | ||||
|                                       min_dist; | ||||
|                 } | ||||
| 
 | ||||
|                 found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); | ||||
|                 found = std::all_of(canplace.begin(), canplace.end(), | ||||
|                                     [](bool v) { return v; }); | ||||
| 
 | ||||
|                 // 20 angles will be tried...
 | ||||
|                 alpha += 0.1 * PI; | ||||
|  | @ -2010,7 +2272,7 @@ public: | |||
|             newpills.reserve(needpillars); | ||||
| 
 | ||||
|             if(found) for(unsigned n = 0; n < needpillars; n++) { | ||||
|                 Vec3d s = spts[n]; double gnd = m_result.ground_level; | ||||
|                 Vec3d s = spts[n];  | ||||
|                 Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); | ||||
|                 p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); | ||||
| 
 | ||||
|  | @ -2075,9 +2337,13 @@ public: | |||
|             // This is only for checking
 | ||||
|             double idist = bridge_mesh_intersect(sph, dir, R, true); | ||||
|             double dist = ray_mesh_intersect(sj, dir); | ||||
|             if (std::isinf(dist)) | ||||
|                 dist = sph(Z) - m_mesh.ground_level() | ||||
|                        + m_mesh.ground_level_offset(); | ||||
| 
 | ||||
|             if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || | ||||
|                std::isinf(dist)  || std::isnan(dist)   || dist < 2*R) { | ||||
|             if(std::isnan(idist) || idist < 2*R || | ||||
|                std::isnan(dist)  || dist  < 2*R) | ||||
|             { | ||||
|                 BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" | ||||
|                                            << " support stick at: " | ||||
|                                            << sj.transpose(); | ||||
|  | @ -2085,7 +2351,7 @@ public: | |||
|             } | ||||
| 
 | ||||
|             Vec3d ej = sj + (dist + HWIDTH_MM)* dir; | ||||
|             m_result.add_compact_bridge(sp, ej, n, R); | ||||
|             m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
|  | @ -2214,7 +2480,9 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points, | |||
|     return pc == ABORT; | ||||
| } | ||||
| 
 | ||||
| SLASupportTree::SLASupportTree(): m_impl(new Impl()) {} | ||||
| SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { | ||||
|     m_impl->ground_level = gnd_lvl; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SLASupportTree::merged_mesh() const | ||||
| { | ||||
|  | @ -2226,7 +2494,7 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { | |||
|     outmesh.merge(get_pad()); | ||||
| } | ||||
| 
 | ||||
| SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const | ||||
| std::vector<ExPolygons> SLASupportTree::slice(float layerh, float init_layerh) const | ||||
| { | ||||
|     if(init_layerh < 0) init_layerh = layerh; | ||||
|     auto& stree = get(); | ||||
|  | @ -2245,36 +2513,31 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const | |||
| 
 | ||||
|     TriangleMesh fullmesh = m_impl->merged_mesh(); | ||||
|     fullmesh.merge(get_pad()); | ||||
|     fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|     if (!fullmesh.empty()) fullmesh.require_shared_vertices(); | ||||
|     TriangleMeshSlicer slicer(&fullmesh); | ||||
|     SlicedSupports ret; | ||||
|     std::vector<ExPolygons> ret; | ||||
|     slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| SlicedSupports SLASupportTree::slice(const std::vector<float> &heights, | ||||
| std::vector<ExPolygons> SLASupportTree::slice(const std::vector<float> &heights, | ||||
|                                      float cr) const | ||||
| { | ||||
|     TriangleMesh fullmesh = m_impl->merged_mesh(); | ||||
|     fullmesh.merge(get_pad()); | ||||
|     fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this
 | ||||
|     if (!fullmesh.empty()) fullmesh.require_shared_vertices(); | ||||
|     TriangleMeshSlicer slicer(&fullmesh); | ||||
|     SlicedSupports ret; | ||||
|     std::vector<ExPolygons> ret; | ||||
|     slicer.slice(heights, cr, &ret, get().ctl().cancelfn); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, | ||||
| const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, | ||||
|                                             const PoolConfig& pcfg) const | ||||
| { | ||||
| //    PoolConfig pcfg;
 | ||||
| //    pcfg.min_wall_thickness_mm = min_wall_thickness_mm;
 | ||||
| //    pcfg.min_wall_height_mm    = min_wall_height_mm;
 | ||||
| //    pcfg.max_merge_distance_mm = max_merge_distance_mm;
 | ||||
| //    pcfg.edge_radius_mm        = edge_radius_mm;
 | ||||
|     return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; | ||||
|     return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; | ||||
| } | ||||
| 
 | ||||
| const TriangleMesh &SLASupportTree::get_pad() const | ||||
|  |  | |||
|  | @ -24,10 +24,11 @@ class TriangleMesh; | |||
| class Model; | ||||
| class ModelInstance; | ||||
| class ModelObject; | ||||
| class Polygon; | ||||
| class ExPolygon; | ||||
| 
 | ||||
| using SliceLayer = std::vector<ExPolygon>; | ||||
| using SlicedSupports = std::vector<SliceLayer>; | ||||
| using Polygons = std::vector<Polygon>; | ||||
| using ExPolygons = std::vector<ExPolygon>; | ||||
| 
 | ||||
| namespace sla { | ||||
| 
 | ||||
|  | @ -81,6 +82,10 @@ struct SupportConfig { | |||
|     // and the model object's bounding box bottom.
 | ||||
|     double object_elevation_mm = 10; | ||||
|      | ||||
|     // The shortest distance between a pillar base perimeter from the model
 | ||||
|     // body. This is only useful when elevation is set to zero.
 | ||||
|     double pillar_base_safety_distance_mm = 0.5; | ||||
| 
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Compile time configuration values (candidates for runtime)
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|  | @ -160,7 +165,7 @@ class SLASupportTree { | |||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     SLASupportTree(); | ||||
|     SLASupportTree(double ground_level = 0.0); | ||||
| 
 | ||||
|     SLASupportTree(const std::vector<SupportPoint>& pts, | ||||
|                    const EigenMesh3D& em, | ||||
|  | @ -179,12 +184,17 @@ public: | |||
|     void merged_mesh_with_pad(TriangleMesh&) const; | ||||
| 
 | ||||
|     /// Get the sliced 2d layers of the support geometry.
 | ||||
|     SlicedSupports slice(float layerh, float init_layerh = -1.0) const; | ||||
|     std::vector<ExPolygons> slice(float layerh, float init_layerh = -1.0) const; | ||||
| 
 | ||||
|     SlicedSupports slice(const std::vector<float>&, float closing_radius) const; | ||||
|     std::vector<ExPolygons> slice(const std::vector<float> &, | ||||
|                                   float closing_radius) const; | ||||
| 
 | ||||
|     /// Adding the "pad" (base pool) under the supports
 | ||||
|     const TriangleMesh& add_pad(const SliceLayer& baseplate, | ||||
|     /// modelbase will be used according to the embed_object flag in PoolConfig.
 | ||||
|     /// If set, the plate will interpreted as the model's intrinsic pad. 
 | ||||
|     /// Otherwise, the modelbase will be unified with the base plate calculated
 | ||||
|     /// from the supports.
 | ||||
|     const TriangleMesh& add_pad(const ExPolygons& modelbase, | ||||
|                                 const PoolConfig& pcfg) const; | ||||
| 
 | ||||
|     /// Get the pad geometry
 | ||||
|  |  | |||
|  | @ -29,69 +29,137 @@ namespace sla { | |||
| using igl::PI; | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * SpatIndex implementation | ||||
|  * PointIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class SpatIndex::Impl { | ||||
| class PointIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index::rtree< SpatElement, | ||||
|     using BoostIndex = boost::geometry::index::rtree< PointIndexEl, | ||||
|                        boost::geometry::index::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| SpatIndex::SpatIndex(): m_impl(new Impl()) {} | ||||
| SpatIndex::~SpatIndex() {} | ||||
| PointIndex::PointIndex(): m_impl(new Impl()) {} | ||||
| PointIndex::~PointIndex() {} | ||||
| 
 | ||||
| SpatIndex::SpatIndex(const SpatIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| SpatIndex::SpatIndex(SpatIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| SpatIndex& SpatIndex::operator=(const SpatIndex &cpy) | ||||
| PointIndex& PointIndex::operator=(const PointIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| SpatIndex& SpatIndex::operator=(SpatIndex &&cpy) | ||||
| PointIndex& PointIndex::operator=(PointIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void SpatIndex::insert(const SpatElement &el) | ||||
| void PointIndex::insert(const PointIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool SpatIndex::remove(const SpatElement& el) | ||||
| bool PointIndex::remove(const PointIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<SpatElement> | ||||
| SpatIndex::query(std::function<bool(const SpatElement &)> fn) | ||||
| std::vector<PointIndexEl> | ||||
| PointIndex::query(std::function<bool(const PointIndexEl &)> fn) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
| 
 | ||||
|     std::vector<SpatElement> ret; | ||||
|     std::vector<PointIndexEl> ret; | ||||
|     m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| std::vector<SpatElement> SpatIndex::nearest(const Vec3d &el, unsigned k = 1) | ||||
| std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     std::vector<SpatElement> ret; ret.reserve(k); | ||||
|     std::vector<PointIndexEl> ret; ret.reserve(k); | ||||
|     m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t SpatIndex::size() const | ||||
| size_t PointIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void SpatIndex::foreach(std::function<void (const SpatElement &)> fn) | ||||
| void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| /* **************************************************************************
 | ||||
|  * BoxIndex implementation | ||||
|  * ************************************************************************** */ | ||||
| 
 | ||||
| class BoxIndex::Impl { | ||||
| public: | ||||
|     using BoostIndex = boost::geometry::index:: | ||||
|         rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>; | ||||
| 
 | ||||
|     BoostIndex m_store; | ||||
| }; | ||||
| 
 | ||||
| BoxIndex::BoxIndex(): m_impl(new Impl()) {} | ||||
| BoxIndex::~BoxIndex() {} | ||||
| 
 | ||||
| BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} | ||||
| BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) | ||||
| { | ||||
|     m_impl.reset(new Impl(*cpy.m_impl)); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) | ||||
| { | ||||
|     m_impl.swap(cpy.m_impl); | ||||
|     return *this; | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::insert(const BoxIndexEl &el) | ||||
| { | ||||
|     m_impl->m_store.insert(el); | ||||
| } | ||||
| 
 | ||||
| bool BoxIndex::remove(const BoxIndexEl& el) | ||||
| { | ||||
|     return m_impl->m_store.remove(el) == 1; | ||||
| } | ||||
| 
 | ||||
| std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb, | ||||
|                                         BoxIndex::QueryType qt) | ||||
| { | ||||
|     namespace bgi = boost::geometry::index; | ||||
|      | ||||
|     std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size()); | ||||
|      | ||||
|     switch (qt) { | ||||
|     case qtIntersects: | ||||
|         m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); | ||||
|         break; | ||||
|     case qtWithin: | ||||
|         m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| size_t BoxIndex::size() const | ||||
| { | ||||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
|  | @ -343,12 +411,14 @@ PointSet normals(const PointSet& points, | |||
|     return ret; | ||||
| } | ||||
| namespace bgi = boost::geometry::index; | ||||
| using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; | ||||
| using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
| ClusteredPoints cluster(Index3D& sindex, unsigned max_points, | ||||
|                         std::function<std::vector<SpatElement>(const Index3D&, const SpatElement&)> qfn) | ||||
| ClusteredPoints cluster(Index3D &sindex, | ||||
|                         unsigned max_points, | ||||
|                         std::function<std::vector<PointIndexEl>( | ||||
|                             const Index3D &, const PointIndexEl &)> qfn) | ||||
| { | ||||
|     using Elems = std::vector<SpatElement>; | ||||
|     using Elems = std::vector<PointIndexEl>; | ||||
| 
 | ||||
|     // Recursive function for visiting all the points in a given distance to
 | ||||
|     // each other
 | ||||
|  | @ -356,8 +426,8 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, | |||
|     [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) | ||||
|     { | ||||
|         for(auto& p : pts) { | ||||
|             std::vector<SpatElement> tmp = qfn(sindex, p); | ||||
|             auto cmp = [](const SpatElement& e1, const SpatElement& e2){ | ||||
|             std::vector<PointIndexEl> tmp = qfn(sindex, p); | ||||
|             auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ | ||||
|                 return e1.second < e2.second; | ||||
|             }; | ||||
| 
 | ||||
|  | @ -401,12 +471,12 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, | |||
| } | ||||
| 
 | ||||
| namespace { | ||||
| std::vector<SpatElement> distance_queryfn(const Index3D& sindex, | ||||
|                                           const SpatElement& p, | ||||
| std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex, | ||||
|                                           const PointIndexEl& p, | ||||
|                                           double dist, | ||||
|                                           unsigned max_points) | ||||
| { | ||||
|     std::vector<SpatElement> tmp; tmp.reserve(max_points); | ||||
|     std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|     sindex.query( | ||||
|         bgi::nearest(p.first, max_points), | ||||
|         std::back_inserter(tmp) | ||||
|  | @ -433,7 +503,7 @@ ClusteredPoints cluster( | |||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const SpatElement& p) | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|     { | ||||
|         return distance_queryfn(sidx, p, dist, max_points); | ||||
|     }); | ||||
|  | @ -443,7 +513,7 @@ ClusteredPoints cluster( | |||
| ClusteredPoints cluster( | ||||
|         const std::vector<unsigned>& indices, | ||||
|         std::function<Vec3d(unsigned)> pointfn, | ||||
|         std::function<bool(const SpatElement&, const SpatElement&)> predicate, | ||||
|         std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, | ||||
|         unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|  | @ -453,10 +523,10 @@ ClusteredPoints cluster( | |||
|     for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|         [max_points, predicate](const Index3D& sidx, const SpatElement& p) | ||||
|         [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) | ||||
|     { | ||||
|         std::vector<SpatElement> tmp; tmp.reserve(max_points); | ||||
|         sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ | ||||
|         std::vector<PointIndexEl> tmp; tmp.reserve(max_points); | ||||
|         sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ | ||||
|             return predicate(p, e); | ||||
|         }), std::back_inserter(tmp)); | ||||
|         return tmp; | ||||
|  | @ -473,7 +543,7 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) | |||
|         sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); | ||||
| 
 | ||||
|     return cluster(sindex, max_points, | ||||
|                    [dist, max_points](const Index3D& sidx, const SpatElement& p) | ||||
|                    [dist, max_points](const Index3D& sidx, const PointIndexEl& p) | ||||
|     { | ||||
|         return distance_queryfn(sidx, p, dist, max_points); | ||||
|     }); | ||||
|  |  | |||
|  | @ -31,11 +31,10 @@ using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>; | |||
| class SLAPrintObject::SupportData | ||||
| { | ||||
| public: | ||||
|     sla::EigenMesh3D emesh;             // index-triangle representation
 | ||||
|     std::vector<sla::SupportPoint> | ||||
|                    support_points;      // all the support points (manual/auto)
 | ||||
|     SupportTreePtr support_tree_ptr;    // the supports
 | ||||
|     SlicedSupports support_slices;      // sliced supports
 | ||||
|     sla::EigenMesh3D emesh;              // index-triangle representation
 | ||||
|     std::vector<sla::SupportPoint> support_points;     // all the support points (manual/auto)
 | ||||
|     SupportTreePtr                 support_tree_ptr;   // the supports
 | ||||
|     std::vector<ExPolygons>        support_slices;     // sliced supports
 | ||||
| 
 | ||||
|     inline SupportData(const TriangleMesh &trmesh) : emesh(trmesh) {} | ||||
| }; | ||||
|  | @ -441,12 +440,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf | |||
|         update_apply_status(this->invalidate_all_steps()); | ||||
|         m_objects = print_objects_new; | ||||
|         // Delete the PrintObjects marked as Unknown or Deleted.
 | ||||
|         bool deleted_objects = false; | ||||
|         for (auto &pos : print_object_status) | ||||
|             if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { | ||||
|                 update_apply_status(pos.print_object->invalidate_all_steps()); | ||||
|                 delete pos.print_object; | ||||
|                 deleted_objects = true; | ||||
|             } | ||||
|         if (new_objects) | ||||
|             update_apply_status(false); | ||||
|  | @ -473,7 +470,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
| 
 | ||||
|     int n_object_steps = int(params.to_object_step) + 1; | ||||
|     if (n_object_steps == 0) | ||||
|         n_object_steps = (int)slaposCount; | ||||
|         n_object_steps = int(slaposCount); | ||||
| 
 | ||||
|     if (params.single_model_object.valid()) { | ||||
|         // Find the print object to be processed with priority.
 | ||||
|  | @ -488,7 +485,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|         // Find out whether the priority print object is being currently processed.
 | ||||
|         bool running = false; | ||||
|         for (int istep = 0; istep < n_object_steps; ++ istep) { | ||||
|             if (! print_object->m_stepmask[istep]) | ||||
|             if (! print_object->m_stepmask[size_t(istep)]) | ||||
|                 // Step was skipped, cancel.
 | ||||
|                 break; | ||||
|             if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { | ||||
|  | @ -504,7 +501,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|         if (params.single_model_instance_only) { | ||||
|             // Suppress all the steps of other instances.
 | ||||
|             for (SLAPrintObject *po : m_objects) | ||||
|                 for (int istep = 0; istep < (int)slaposCount; ++ istep) | ||||
|                 for (size_t istep = 0; istep < slaposCount; ++ istep) | ||||
|                     po->m_stepmask[istep] = false; | ||||
|         } else if (! running) { | ||||
|             // Swap the print objects, so that the selected print_object is first in the row.
 | ||||
|  | @ -514,15 +511,15 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|         } | ||||
|         // and set the steps for the current object.
 | ||||
|         for (int istep = 0; istep < n_object_steps; ++ istep) | ||||
|             print_object->m_stepmask[istep] = true; | ||||
|         for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) | ||||
|             print_object->m_stepmask[istep] = false; | ||||
|             print_object->m_stepmask[size_t(istep)] = true; | ||||
|         for (int istep = n_object_steps; istep < int(slaposCount); ++ istep) | ||||
|             print_object->m_stepmask[size_t(istep)] = false; | ||||
|     } else { | ||||
|         // Slicing all objects.
 | ||||
|         bool running = false; | ||||
|         for (SLAPrintObject *print_object : m_objects) | ||||
|             for (int istep = 0; istep < n_object_steps; ++ istep) { | ||||
|                 if (! print_object->m_stepmask[istep]) { | ||||
|                 if (! print_object->m_stepmask[size_t(istep)]) { | ||||
|                     // Step may have been skipped. Restart.
 | ||||
|                     goto loop_end; | ||||
|                 } | ||||
|  | @ -538,8 +535,8 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
|             this->call_cancel_callback(); | ||||
|         for (SLAPrintObject *po : m_objects) { | ||||
|             for (int istep = 0; istep < n_object_steps; ++ istep) | ||||
|                 po->m_stepmask[istep] = true; | ||||
|             for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) | ||||
|                 po->m_stepmask[size_t(istep)] = true; | ||||
|             for (auto istep = size_t(n_object_steps); istep < slaposCount; ++ istep) | ||||
|                 po->m_stepmask[istep] = false; | ||||
|         } | ||||
|     } | ||||
|  | @ -557,9 +554,9 @@ void SLAPrint::set_task(const TaskParams ¶ms) | |||
| void SLAPrint::finalize() | ||||
| { | ||||
|     for (SLAPrintObject *po : m_objects) | ||||
|         for (int istep = 0; istep < (int)slaposCount; ++ istep) | ||||
|         for (size_t istep = 0; istep < slaposCount; ++ istep) | ||||
|             po->m_stepmask[istep] = true; | ||||
|     for (int istep = 0; istep < (int)slapsCount; ++ istep) | ||||
|     for (size_t istep = 0; istep < slapsCount; ++ istep) | ||||
|         m_stepmask[istep] = true; | ||||
| } | ||||
| 
 | ||||
|  | @ -597,21 +594,48 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { | |||
|     scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); | ||||
|     scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); | ||||
|     scfg.base_height_mm = c.support_base_height.getFloat(); | ||||
|     scfg.pillar_base_safety_distance_mm = | ||||
|         c.support_base_safety_distance.getFloat() < EPSILON ? | ||||
|         scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); | ||||
|      | ||||
|     return scfg; | ||||
| } | ||||
| 
 | ||||
| sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { | ||||
|     sla::PoolConfig::EmbedObject ret; | ||||
|      | ||||
|     ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && | ||||
|                   c.pad_enable.getBool() && c.supports_enable.getBool(); | ||||
|      | ||||
|     if(ret.enabled) { | ||||
|         ret.object_gap_mm        = c.pad_object_gap.getFloat(); | ||||
|         ret.stick_width_mm       = c.pad_object_connector_width.getFloat(); | ||||
|         ret.stick_stride_mm      = c.pad_object_connector_stride.getFloat(); | ||||
|         ret.stick_penetration_mm = c.pad_object_connector_penetration | ||||
|                                     .getFloat(); | ||||
|     } | ||||
|      | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { | ||||
|     sla::PoolConfig pcfg; | ||||
| 
 | ||||
|     pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); | ||||
|     pcfg.wall_slope = c.pad_wall_slope.getFloat(); | ||||
|     pcfg.edge_radius_mm = c.pad_edge_radius.getFloat(); | ||||
|     pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; | ||||
|      | ||||
|     // We do not support radius for now
 | ||||
|     pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat();
 | ||||
|      | ||||
|     pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); | ||||
|     pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); | ||||
| 
 | ||||
|     // set builtin pad implicitly ON
 | ||||
|     pcfg.embed_object = builtin_pad_cfg(c); | ||||
|      | ||||
|     return pcfg; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| std::string SLAPrint::validate() const | ||||
|  | @ -635,8 +659,20 @@ std::string SLAPrint::validate() const | |||
|                 2 * cfg.head_back_radius_mm - | ||||
|                 cfg.head_penetration_mm; | ||||
|          | ||||
|         if(supports_en && pinhead_width > cfg.object_elevation_mm) | ||||
|         double elv = cfg.object_elevation_mm; | ||||
| 
 | ||||
|         if(supports_en && elv > EPSILON && elv < pinhead_width ) | ||||
|             return L("Elevation is too low for object."); | ||||
|          | ||||
|         sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); | ||||
|         if(supports_en && builtinpad.enabled && | ||||
|            cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { | ||||
|             return L( | ||||
|                 "The endings of the support pillars will be deployed on the " | ||||
|                 "gap between the object and the pad. 'Support base safety " | ||||
|                 "distance' has to be greater than the 'Pad object gap' " | ||||
|                 "parameter to avoid this."); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return ""; | ||||
|  | @ -751,18 +787,27 @@ void SLAPrint::process() | |||
|              | ||||
|             mit->set_model_slice_idx(po, id); ++mit; | ||||
|         } | ||||
| 
 | ||||
|         if(po.m_config.supports_enable.getBool() || | ||||
|            po.m_config.pad_enable.getBool()) | ||||
|         { | ||||
|             po.m_supportdata.reset( | ||||
|                 new SLAPrintObject::SupportData(po.transformed_mesh()) ); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // In this step we check the slices, identify island and cover them with
 | ||||
|     // support points. Then we sprinkle the rest of the mesh.
 | ||||
|     auto support_points = [this, ostepd](SLAPrintObject& po) { | ||||
|         const ModelObject& mo = *po.m_model_object; | ||||
|         po.m_supportdata.reset( | ||||
|                     new SLAPrintObject::SupportData(po.transformed_mesh()) ); | ||||
| 
 | ||||
|         // If supports are disabled, we can skip the model scan.
 | ||||
|         if(!po.m_config.supports_enable.getBool()) return; | ||||
| 
 | ||||
|         if (!po.m_supportdata) | ||||
|             po.m_supportdata.reset( | ||||
|                 new SLAPrintObject::SupportData(po.transformed_mesh())); | ||||
| 
 | ||||
|         const ModelObject& mo = *po.m_model_object; | ||||
| 
 | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Support point count " | ||||
|                                  << mo.sla_support_points.size(); | ||||
| 
 | ||||
|  | @ -771,7 +816,7 @@ void SLAPrint::process() | |||
|         // into the backend cache.
 | ||||
|         if (mo.sla_points_status != sla::PointsStatus::UserModified) { | ||||
| 
 | ||||
|             // Hypotetical use of the slice index:
 | ||||
|             // Hypothetical use of the slice index:
 | ||||
|             // auto bb = po.transformed_mesh().bounding_box();
 | ||||
|             // auto range = po.get_slice_records(bb.min(Z));
 | ||||
|             // std::vector<float> heights; heights.reserve(range.size());
 | ||||
|  | @ -820,13 +865,40 @@ void SLAPrint::process() | |||
|             BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " | ||||
|                                      << po.m_supportdata->support_points.size(); | ||||
| 
 | ||||
|             // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports
 | ||||
|             m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); | ||||
|             // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
 | ||||
|             // the update status to GLGizmoSlaSupports
 | ||||
|             m_report_status(*this, | ||||
|                             -1, | ||||
|                             L("Generating support points"), | ||||
|                             SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); | ||||
|         } | ||||
|         else { | ||||
|             // There are either some points on the front-end, or the user removed them on purpose. No calculation will be done.
 | ||||
|             // There are either some points on the front-end, or the user
 | ||||
|             // removed them on purpose. No calculation will be done.
 | ||||
|             po.m_supportdata->support_points = po.transformed_support_points(); | ||||
|         } | ||||
|          | ||||
|         // If the zero elevation mode is engaged, we have to filter out all the
 | ||||
|         // points that are on the bottom of the object
 | ||||
|         if (po.config().support_object_elevation.getFloat() <= EPSILON) { | ||||
|             double gnd       = po.m_supportdata->emesh.ground_level(); | ||||
|             auto & pts       = po.m_supportdata->support_points; | ||||
|             double tolerance = po.config().pad_enable.getBool() | ||||
|                                    ? po.m_config.pad_wall_thickness.getFloat() | ||||
|                                    : po.m_config.support_base_height.getFloat(); | ||||
| 
 | ||||
|             // get iterator to the reorganized vector end
 | ||||
|             auto endit = std::remove_if( | ||||
|                 pts.begin(), | ||||
|                 pts.end(), | ||||
|                 [tolerance, gnd](const sla::SupportPoint &sp) { | ||||
|                     double diff = std::abs(gnd - double(sp.pos(Z))); | ||||
|                     return diff <= tolerance; | ||||
|                 }); | ||||
|              | ||||
|             // erase all elements after the new end
 | ||||
|             pts.erase(endit, pts.end()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // In this step we create the supports
 | ||||
|  | @ -834,9 +906,18 @@ void SLAPrint::process() | |||
|     { | ||||
|         if(!po.m_supportdata) return; | ||||
|          | ||||
|         sla::PoolConfig pcfg = make_pool_config(po.m_config); | ||||
| 
 | ||||
|         if (pcfg.embed_object) | ||||
|             po.m_supportdata->emesh.ground_level_offset( | ||||
|                 pcfg.min_wall_thickness_mm); | ||||
| 
 | ||||
|         if(!po.m_config.supports_enable.getBool()) { | ||||
|              | ||||
|             // Generate empty support tree. It can still host a pad
 | ||||
|             po.m_supportdata->support_tree_ptr.reset(new SLASupportTree()); | ||||
|             po.m_supportdata->support_tree_ptr.reset( | ||||
|                     new SLASupportTree(po.m_supportdata->emesh.ground_level())); | ||||
|              | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|  | @ -888,40 +969,33 @@ void SLAPrint::process() | |||
|         // and before the supports had been sliced. (or the slicing has to be
 | ||||
|         // repeated)
 | ||||
| 
 | ||||
|         if(!po.m_supportdata || !po.m_supportdata->support_tree_ptr) { | ||||
|             BOOST_LOG_TRIVIAL(error) << "Uninitialized support data at " | ||||
|                                      << "pad creation."; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if(po.m_config.pad_enable.getBool()) | ||||
|         { | ||||
|             double wt = po.m_config.pad_wall_thickness.getFloat(); | ||||
|             double h =  po.m_config.pad_wall_height.getFloat(); | ||||
|             double md = po.m_config.pad_max_merge_distance.getFloat(); | ||||
|             // Radius is disabled for now...
 | ||||
|             double er = 0; // po.m_config.pad_edge_radius.getFloat();
 | ||||
|             double tilt = po.m_config.pad_wall_slope.getFloat()  * PI / 180.0; | ||||
|             double lh = po.m_config.layer_height.getFloat(); | ||||
|             double elevation = po.m_config.support_object_elevation.getFloat(); | ||||
|             if(!po.m_config.supports_enable.getBool()) elevation = 0; | ||||
|             sla::PoolConfig pcfg(wt, h, md, er, tilt); | ||||
|             // Get the distilled pad configuration from the config
 | ||||
|             sla::PoolConfig pcfg = make_pool_config(po.m_config); | ||||
| 
 | ||||
|             ExPolygons bp; | ||||
|             double pad_h = sla::get_pad_fullheight(pcfg); | ||||
|             auto&& trmesh = po.transformed_mesh(); | ||||
|             ExPolygons bp; // This will store the base plate of the pad.
 | ||||
|             double   pad_h             = sla::get_pad_fullheight(pcfg); | ||||
|             const TriangleMesh &trmesh = po.transformed_mesh(); | ||||
| 
 | ||||
|             // This call can get pretty time consuming
 | ||||
|             auto thrfn = [this](){ throw_if_canceled(); }; | ||||
| 
 | ||||
|             if(elevation < pad_h) { | ||||
|                 // we have to count with the model geometry for the base plate
 | ||||
|                 sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn); | ||||
|             if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { | ||||
|                 // No support (thus no elevation) or zero elevation mode
 | ||||
|                 // we sometimes call it "builtin pad" is enabled so we will
 | ||||
|                 // get a sample from the bottom of the mesh and use it for pad
 | ||||
|                 // creation.
 | ||||
|                 sla::base_plate(trmesh, | ||||
|                                 bp, | ||||
|                                 float(pad_h), | ||||
|                                 float(po.m_config.layer_height.getFloat()), | ||||
|                                 thrfn); | ||||
|             } | ||||
| 
 | ||||
|             pcfg.throw_on_cancel = thrfn; | ||||
|             po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); | ||||
|         } else { | ||||
|         } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { | ||||
|             po.m_supportdata->support_tree_ptr->remove_pad(); | ||||
|         } | ||||
| 
 | ||||
|  | @ -938,6 +1012,11 @@ void SLAPrint::process() | |||
| 
 | ||||
|         if(sd) sd->support_slices.clear(); | ||||
| 
 | ||||
|         // Don't bother if no supports and no pad is present.
 | ||||
|         if (!po.m_config.supports_enable.getBool() && | ||||
|             !po.m_config.pad_enable.getBool()) | ||||
|             return; | ||||
| 
 | ||||
|         if(sd && sd->support_tree_ptr) { | ||||
| 
 | ||||
|             std::vector<float> heights; heights.reserve(po.m_slice_index.size()); | ||||
|  | @ -964,7 +1043,8 @@ void SLAPrint::process() | |||
|             po.m_slice_index[i].set_support_slice_idx(po, i); | ||||
|         } | ||||
| 
 | ||||
|         // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices.
 | ||||
|         // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
 | ||||
|         // status to the 3D preview to load the SLA slices.
 | ||||
|         m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); | ||||
|     }; | ||||
| 
 | ||||
|  | @ -1536,14 +1616,17 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): | ||||
|     Inherited(print, model_object), | ||||
|     m_stepmask(slaposCount, true), | ||||
|     m_transformed_rmesh( [this](TriangleMesh& obj){ | ||||
|             obj = m_model_object->raw_mesh(); obj.transform(m_trafo); obj.require_shared_vertices(); | ||||
|         }) | ||||
| { | ||||
| } | ||||
| SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object) | ||||
|     : Inherited(print, model_object) | ||||
|     , m_stepmask(slaposCount, true) | ||||
|     , m_transformed_rmesh([this](TriangleMesh &obj) { | ||||
|         obj = m_model_object->raw_mesh(); | ||||
|         if (!obj.empty()) { | ||||
|             obj.transform(m_trafo); | ||||
|             obj.require_shared_vertices(); | ||||
|         } | ||||
|     }) | ||||
| {} | ||||
| 
 | ||||
| SLAPrintObject::~SLAPrintObject() {} | ||||
| 
 | ||||
|  | @ -1582,13 +1665,19 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf | |||
|             || opt_key == "support_critical_angle" | ||||
|             || opt_key == "support_max_bridge_length" | ||||
|             || opt_key == "support_max_pillar_link_distance" | ||||
|             || opt_key == "support_base_safety_distance" | ||||
|             ) { | ||||
|             steps.emplace_back(slaposSupportTree); | ||||
|         } else if ( | ||||
|                opt_key == "pad_wall_height" | ||||
|             || opt_key == "pad_max_merge_distance" | ||||
|             || opt_key == "pad_wall_slope" | ||||
|             || opt_key == "pad_edge_radius") { | ||||
|             || opt_key == "pad_edge_radius" | ||||
|             || opt_key == "pad_object_gap" | ||||
|             || opt_key == "pad_object_connector_stride" | ||||
|             || opt_key == "pad_object_connector_width" | ||||
|             || opt_key == "pad_object_connector_penetration" | ||||
|             ) { | ||||
|             steps.emplace_back(slaposBasePool); | ||||
|         } else { | ||||
|             // All keys should be covered.
 | ||||
|  | @ -1629,17 +1718,16 @@ bool SLAPrintObject::invalidate_all_steps() | |||
| } | ||||
| 
 | ||||
| double SLAPrintObject::get_elevation() const { | ||||
|     bool se = m_config.supports_enable.getBool(); | ||||
|     double ret = se? m_config.support_object_elevation.getFloat() : 0; | ||||
|     bool   en  = m_config.supports_enable.getBool(); | ||||
|     double ret = en ? m_config.support_object_elevation.getFloat() : 0.; | ||||
| 
 | ||||
|     // if the pad is enabled, then half of the pad height is its base plate
 | ||||
|     if(m_config.pad_enable.getBool()) { | ||||
|         // Normally the elevation for the pad itself would be the thickness of
 | ||||
|         // its walls but currently it is half of its thickness. Whatever it
 | ||||
|         // will be in the future, we provide the config to the get_pad_elevation
 | ||||
|         // method and we will have the correct value
 | ||||
|         sla::PoolConfig pcfg = make_pool_config(m_config); | ||||
|         ret += sla::get_pad_elevation(pcfg); | ||||
|         if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg); | ||||
|     } | ||||
| 
 | ||||
|     return ret; | ||||
|  | @ -1647,14 +1735,14 @@ double SLAPrintObject::get_elevation() const { | |||
| 
 | ||||
| double SLAPrintObject::get_current_elevation() const | ||||
| { | ||||
|     bool se = m_config.supports_enable.getBool(); | ||||
|     bool has_supports = is_step_done(slaposSupportTree); | ||||
|     bool has_pad = is_step_done(slaposBasePool); | ||||
| 
 | ||||
|     if(!has_supports && !has_pad) | ||||
|         return 0; | ||||
|     else if(has_supports && !has_pad) | ||||
|         return se ? m_config.support_object_elevation.getFloat() : 0; | ||||
|     else if(has_supports && !has_pad) { | ||||
|         return m_config.support_object_elevation.getFloat(); | ||||
|     } | ||||
| 
 | ||||
|     return get_elevation(); | ||||
| } | ||||
|  | @ -1682,13 +1770,14 @@ namespace { // dummy empty static containers for return values in some methods | |||
| const std::vector<ExPolygons> EMPTY_SLICES; | ||||
| const TriangleMesh EMPTY_MESH; | ||||
| const ExPolygons EMPTY_SLICE; | ||||
| const std::vector<sla::SupportPoint> EMPTY_SUPPORT_POINTS; | ||||
| } | ||||
| 
 | ||||
| const SliceRecord SliceRecord::EMPTY(0, std::nanf(""), 0.f); | ||||
| 
 | ||||
| const std::vector<sla::SupportPoint>& SLAPrintObject::get_support_points() const | ||||
| { | ||||
|     return m_supportdata->support_points; | ||||
|     return m_supportdata? m_supportdata->support_points : EMPTY_SUPPORT_POINTS; | ||||
| } | ||||
| 
 | ||||
| const std::vector<ExPolygons> &SLAPrintObject::get_support_slices() const | ||||
|  |  | |||
|  | @ -41,13 +41,4 @@ | |||
| #define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7) | ||||
| 
 | ||||
| 
 | ||||
| //====================
 | ||||
| // 1.42.0.alpha8 techs
 | ||||
| //====================
 | ||||
| #define ENABLE_1_42_0_ALPHA8 1 | ||||
| 
 | ||||
| // Toolbars and Gizmos use icons imported from svg files
 | ||||
| #define ENABLE_SVG_ICONS (1 && ENABLE_1_42_0_ALPHA8 && ENABLE_TEXTURES_FROM_SVG) | ||||
| 
 | ||||
| 
 | ||||
| #endif // _technologies_h_
 | ||||
|  |  | |||
|  | @ -410,6 +410,8 @@ public: | |||
|     BoundingBoxf3        transformed_convex_hull_bounding_box(const Transform3d &trafo) const; | ||||
|     // caching variant
 | ||||
|     const BoundingBoxf3& transformed_convex_hull_bounding_box() const; | ||||
|     // convex hull
 | ||||
|     const TriangleMesh*  convex_hull() const { return m_convex_hull.get(); } | ||||
| 
 | ||||
|     bool                empty() const { return this->indexed_vertex_array.empty(); } | ||||
| 
 | ||||
|  |  | |||
|  | @ -229,6 +229,33 @@ std::string AppConfig::get_last_dir() const | |||
|     return std::string(); | ||||
| } | ||||
| 
 | ||||
| std::vector<std::string> AppConfig::get_recent_projects() const | ||||
| { | ||||
|     std::vector<std::string> ret; | ||||
|     const auto it = m_storage.find("recent_projects"); | ||||
|     if (it != m_storage.end()) | ||||
|     { | ||||
|         for (const std::map<std::string, std::string>::value_type& item : it->second) | ||||
|         { | ||||
|             ret.push_back(item.second); | ||||
|         } | ||||
|     } | ||||
|     return ret; | ||||
| } | ||||
| 
 | ||||
| void AppConfig::set_recent_projects(const std::vector<std::string>& recent_projects) | ||||
| { | ||||
|     auto it = m_storage.find("recent_projects"); | ||||
|     if (it == m_storage.end()) | ||||
|         it = m_storage.insert(std::map<std::string, std::map<std::string, std::string>>::value_type("recent_projects", std::map<std::string, std::string>())).first; | ||||
| 
 | ||||
|     it->second.clear(); | ||||
|     for (unsigned int i = 0; i < (unsigned int)recent_projects.size(); ++i) | ||||
|     { | ||||
|         it->second[std::to_string(i + 1)] = recent_projects[i]; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void AppConfig::update_config_dir(const std::string &dir) | ||||
| { | ||||
|     this->set("recent", "config_directory", dir); | ||||
|  |  | |||
|  | @ -122,6 +122,9 @@ public: | |||
| 	// Does the config file exist?
 | ||||
| 	static bool 		exists(); | ||||
| 
 | ||||
|     std::vector<std::string> get_recent_projects() const; | ||||
|     void set_recent_projects(const std::vector<std::string>& recent_projects); | ||||
| 
 | ||||
| private: | ||||
| 	// Map of section, name -> value
 | ||||
| 	std::map<std::string, std::map<std::string, std::string>> 	m_storage; | ||||
|  |  | |||
|  | @ -1199,11 +1199,7 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar | |||
|     , m_bed(bed) | ||||
|     , m_camera(camera) | ||||
|     , m_view_toolbar(view_toolbar) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_toolbar(GLToolbar::Normal, "Top") | ||||
| #else | ||||
|     , m_toolbar(GLToolbar::Normal) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_use_clipping_planes(false) | ||||
|     , m_sidebar_field("") | ||||
|     , m_keep_dirty(false) | ||||
|  | @ -3422,12 +3418,6 @@ bool GLCanvas3D::_init_toolbar() | |||
|     if (!m_toolbar.is_enabled()) | ||||
|         return true; | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     ItemsIconsTexture::Metadata icons_data; | ||||
|     icons_data.filename = "toolbar.png"; | ||||
|     icons_data.icon_size = 37; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     BackgroundTexture::Metadata background_data; | ||||
|     background_data.filename = "toolbar_background.png"; | ||||
|     background_data.left = 16; | ||||
|  | @ -3435,11 +3425,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|     background_data.right = 16; | ||||
|     background_data.bottom = 16; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (!m_toolbar.init(background_data)) | ||||
| #else | ||||
|     if (!m_toolbar.init(icons_data, background_data)) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     { | ||||
|         // unable to init the toolbar texture, disable it
 | ||||
|         m_toolbar.set_enabled(false); | ||||
|  | @ -3456,9 +3442,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|     GLToolbarItem::Data item; | ||||
| 
 | ||||
|     item.name = "add"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "add.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]"; | ||||
|     item.sprite_id = 0; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); }; | ||||
|  | @ -3466,9 +3450,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "delete"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "remove.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Delete")) + " [Del]"; | ||||
|     item.sprite_id = 1; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); }; | ||||
|  | @ -3477,9 +3459,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "deleteall"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "delete_all.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Delete all")) + " [" + GUI::shortkey_ctrl_prefix() + "Del]"; | ||||
|     item.sprite_id = 2; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); }; | ||||
|  | @ -3488,9 +3468,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "arrange"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "arrange.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Arrange")) + " [A]"; | ||||
|     item.sprite_id = 3; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); }; | ||||
|  | @ -3502,9 +3480,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "copy"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "copy.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Copy")) + " [" + GUI::shortkey_ctrl_prefix() + "C]"; | ||||
|     item.sprite_id = 4; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); }; | ||||
|  | @ -3513,9 +3489,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "paste"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "paste.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Paste")) + " [" + GUI::shortkey_ctrl_prefix() + "V]"; | ||||
|     item.sprite_id = 5; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); }; | ||||
|  | @ -3527,9 +3501,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "more"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "instance_add.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Add instance")) + " [+]"; | ||||
|     item.sprite_id = 6; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); }; | ||||
|  | @ -3539,9 +3511,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "fewer"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "instance_remove.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Remove instance")) + " [-]"; | ||||
|     item.sprite_id = 7; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); }; | ||||
|  | @ -3554,9 +3524,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "splitobjects"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "split_objects.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Split to objects")); | ||||
|     item.sprite_id = 8; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); }; | ||||
|  | @ -3566,9 +3534,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "splitvolumes"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "split_parts.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Split to parts")); | ||||
|     item.sprite_id = 9; | ||||
|     item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); }; | ||||
|  | @ -3581,9 +3547,7 @@ bool GLCanvas3D::_init_toolbar() | |||
|         return false; | ||||
| 
 | ||||
|     item.name = "layersediting"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "layers_white.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Layers editing")); | ||||
|     item.sprite_id = 10; | ||||
|     item.is_toggable = true; | ||||
|  | @ -3921,9 +3885,6 @@ void GLCanvas3D::_render_overlays() const | |||
|     _render_gizmos_overlay(); | ||||
|     _render_warning_texture(); | ||||
|     _render_legend_texture(); | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     _resize_toolbars(); | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
|     _render_toolbar(); | ||||
|     _render_view_toolbar(); | ||||
| 
 | ||||
|  | @ -4000,7 +3961,6 @@ void GLCanvas3D::_render_current_gizmo() const | |||
| 
 | ||||
| void GLCanvas3D::_render_gizmos_overlay() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
| #if ENABLE_RETINA_GL | ||||
| //     m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor());
 | ||||
|     const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale(); | ||||
|  | @ -4011,14 +3971,12 @@ void GLCanvas3D::_render_gizmos_overlay() const | |||
|     const float size = int(GLGizmosManager::Default_Icons_Size*wxGetApp().toolbar_icon_scale()); | ||||
|     m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment
 | ||||
| #endif /* __WXMSW__ */ | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     m_gizmos.render_overlay(*this, m_selection); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_render_toolbar() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
| #if ENABLE_RETINA_GL | ||||
| //     m_toolbar.set_scale(m_retina_helper->get_scale_factor());
 | ||||
|     const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true); | ||||
|  | @ -4073,20 +4031,12 @@ void GLCanvas3D::_render_toolbar() const | |||
|     } | ||||
|     } | ||||
|     m_toolbar.set_position(top, left); | ||||
| #else | ||||
| #if ENABLE_RETINA_GL | ||||
|     m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); | ||||
| #else | ||||
|     m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); | ||||
| #endif /* __WXMSW__ */ | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     m_toolbar.render(*this); | ||||
| } | ||||
| 
 | ||||
| void GLCanvas3D::_render_view_toolbar() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
| #if ENABLE_RETINA_GL | ||||
| //     m_view_toolbar.set_scale(m_retina_helper->get_scale_factor());
 | ||||
|     const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(); | ||||
|  | @ -4106,13 +4056,6 @@ void GLCanvas3D::_render_view_toolbar() const | |||
|     float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; | ||||
|     float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; | ||||
|     m_view_toolbar.set_position(top, left); | ||||
| #else | ||||
| #if ENABLE_RETINA_GL | ||||
|     m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); | ||||
| #else | ||||
|     m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); | ||||
| #endif /* __WXMSW__ */ | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     m_view_toolbar.render(*this); | ||||
| } | ||||
| 
 | ||||
|  | @ -5555,73 +5498,6 @@ bool GLCanvas3D::_is_any_volume_outside() const | |||
|     return false; | ||||
| } | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
| void GLCanvas3D::_resize_toolbars() const | ||||
| { | ||||
|     Size cnv_size = get_canvas_size(); | ||||
|     float zoom = (float)m_camera.get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| 
 | ||||
| #if ENABLE_RETINA_GL | ||||
|     m_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); | ||||
| #else | ||||
|     m_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); | ||||
| #endif /* __WXMSW__ */ | ||||
| 
 | ||||
|     GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation(); | ||||
| 
 | ||||
|     switch (m_toolbar.get_layout_type()) | ||||
|     { | ||||
|     default: | ||||
|     case GLToolbar::Layout::Horizontal: | ||||
|     { | ||||
|         // centers the toolbar on the top edge of the 3d scene
 | ||||
|         float top, left; | ||||
|         if (orientation == GLToolbar::Layout::Top) | ||||
|         { | ||||
|             top = 0.5f * (float)cnv_size.get_height() * inv_zoom; | ||||
|             left = -0.5f * m_toolbar.get_width() * inv_zoom; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; | ||||
|             left = -0.5f * m_toolbar.get_width() * inv_zoom; | ||||
|         } | ||||
|         m_toolbar.set_position(top, left); | ||||
|         break; | ||||
|     } | ||||
|     case GLToolbar::Layout::Vertical: | ||||
|     { | ||||
|         // centers the toolbar on the right edge of the 3d scene
 | ||||
|         float top, left; | ||||
|         if (orientation == GLToolbar::Layout::Left) | ||||
|         { | ||||
|             top = 0.5f * m_toolbar.get_height() * inv_zoom; | ||||
|             left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             top = 0.5f * m_toolbar.get_height() * inv_zoom; | ||||
|             left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom; | ||||
|         } | ||||
|         m_toolbar.set_position(top, left); | ||||
|         break; | ||||
|     } | ||||
|     } | ||||
| 
 | ||||
| #if ENABLE_RETINA_GL | ||||
|     m_view_toolbar.set_icons_scale(m_retina_helper->get_scale_factor()); | ||||
| #else | ||||
|     m_view_toolbar.set_icons_scale(m_canvas->GetContentScaleFactor()); | ||||
| #endif /* __WXMSW__ */ | ||||
| 
 | ||||
|     // places the toolbar on the bottom-left corner of the 3d scene
 | ||||
|     float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; | ||||
|     float left = -0.5f * (float)cnv_size.get_width() * inv_zoom; | ||||
|     m_view_toolbar.set_position(top, left); | ||||
| } | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| void GLCanvas3D::_update_selection_from_hover() | ||||
| { | ||||
|     bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); | ||||
|  |  | |||
|  | @ -728,10 +728,6 @@ private: | |||
| 
 | ||||
|     bool _is_any_volume_outside() const; | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     void _resize_toolbars() const; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // updates the selection from the content of m_hover_volume_idxs
 | ||||
|     void _update_selection_from_hover(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,9 +38,7 @@ const GLToolbarItem::EnabledStateCallback GLToolbarItem::Default_Enabled_State_C | |||
| 
 | ||||
| GLToolbarItem::Data::Data() | ||||
|     : name("") | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , icon_filename("") | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , tooltip("") | ||||
|     , sprite_id(-1) | ||||
|     , is_toggable(false) | ||||
|  | @ -105,14 +103,6 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int | |||
|     return uvs; | ||||
| } | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
| ItemsIconsTexture::Metadata::Metadata() | ||||
|     : filename("") | ||||
|     , icon_size(0) | ||||
| { | ||||
| } | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| BackgroundTexture::Metadata::Metadata() | ||||
|     : filename("") | ||||
|     , left(0) | ||||
|  | @ -122,9 +112,7 @@ BackgroundTexture::Metadata::Metadata() | |||
| { | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| const float GLToolbar::Default_Icons_Size = 40.0f; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| GLToolbar::Layout::Layout() | ||||
|     : type(Horizontal) | ||||
|  | @ -134,31 +122,19 @@ GLToolbar::Layout::Layout() | |||
|     , border(0.0f) | ||||
|     , separator_size(0.0f) | ||||
|     , gap_size(0.0f) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , icons_size(Default_Icons_Size) | ||||
|     , scale(1.0f) | ||||
| #else | ||||
|     , icons_scale(1.0f) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , width(0.0f) | ||||
|     , height(0.0f) | ||||
|     , dirty(true) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name) | ||||
| #else | ||||
| GLToolbar::GLToolbar(GLToolbar::EType type) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     : m_type(type) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_name(name) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_enabled(false) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_icons_texture_dirty(true) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_tooltip("") | ||||
| { | ||||
| } | ||||
|  | @ -171,27 +147,13 @@ GLToolbar::~GLToolbar() | |||
|     } | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) | ||||
| #else | ||||
| bool GLToolbar::init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (m_background_texture.texture.get_id() != 0) | ||||
|         return true; | ||||
| 
 | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|     bool res = false; | ||||
| #else | ||||
|     if (m_icons_texture.texture.get_id() != 0) | ||||
|         return true; | ||||
| 
 | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|     bool res = !icons_texture.filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture.filename, false, true); | ||||
|     if (res) | ||||
|         m_icons_texture.metadata = icons_texture; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     if (!background_texture.filename.empty()) | ||||
|         res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); | ||||
|  | @ -247,7 +209,6 @@ void GLToolbar::set_gap_size(float size) | |||
|     m_layout.dirty = true; | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| void GLToolbar::set_icons_size(float size) | ||||
| { | ||||
|     if (m_layout.icons_size != size) | ||||
|  | @ -267,13 +228,6 @@ void GLToolbar::set_scale(float scale) | |||
|         m_icons_texture_dirty = true; | ||||
|     } | ||||
| } | ||||
| #else | ||||
| void GLToolbar::set_icons_scale(float scale) | ||||
| { | ||||
|     m_layout.icons_scale = scale; | ||||
|     m_layout.dirty = true; | ||||
| } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| bool GLToolbar::is_enabled() const | ||||
| { | ||||
|  | @ -385,10 +339,8 @@ void GLToolbar::render(const GLCanvas3D& parent) const | |||
|     if (!m_enabled || m_items.empty()) | ||||
|         return; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (m_icons_texture_dirty) | ||||
|         generate_icons_texture(); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     switch (m_layout.type) | ||||
|     { | ||||
|  | @ -492,20 +444,12 @@ float GLToolbar::get_width_horizontal() const | |||
| 
 | ||||
| float GLToolbar::get_width_vertical() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; | ||||
| #else | ||||
|     return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| } | ||||
| 
 | ||||
| float GLToolbar::get_height_horizontal() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     return (2.0f * m_layout.border + m_layout.icons_size) * m_layout.scale; | ||||
| #else | ||||
|     return 2.0f * m_layout.border * m_layout.icons_scale + m_icons_texture.metadata.icon_size * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| } | ||||
| 
 | ||||
| float GLToolbar::get_height_vertical() const | ||||
|  | @ -515,7 +459,6 @@ float GLToolbar::get_height_vertical() const | |||
| 
 | ||||
| float GLToolbar::get_main_size() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float size = 2.0f * m_layout.border; | ||||
|     for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) | ||||
|     { | ||||
|  | @ -531,25 +474,7 @@ float GLToolbar::get_main_size() const | |||
|     if (m_items.size() > 1) | ||||
|         size += ((float)m_items.size() - 1.0f) * m_layout.gap_size; | ||||
| 
 | ||||
|     size *= m_layout.scale; | ||||
| #else | ||||
|     float size = 2.0f * m_layout.border * m_layout.icons_scale; | ||||
|     for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i) | ||||
|     { | ||||
|         if (!m_items[i]->is_visible()) | ||||
|             continue; | ||||
| 
 | ||||
|         if (m_items[i]->is_separator()) | ||||
|             size += m_layout.separator_size * m_layout.icons_scale; | ||||
|         else | ||||
|             size += (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale; | ||||
|     } | ||||
| 
 | ||||
|     if (m_items.size() > 1) | ||||
|         size += ((float)m_items.size() - 1.0f) * m_layout.gap_size * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     return size; | ||||
|     return size * m_layout.scale; | ||||
| } | ||||
| 
 | ||||
| void GLToolbar::do_action(unsigned int item_id, GLCanvas3D& parent) | ||||
|  | @ -609,20 +534,12 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -714,20 +631,12 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -831,20 +740,12 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -914,20 +815,12 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& | |||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = m_layout.scale * inv_zoom; | ||||
| #else | ||||
|     float factor = m_layout.icons_scale * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Size cnv_size = parent.get_canvas_size(); | ||||
|     Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -993,34 +886,15 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& | |||
| 
 | ||||
| void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     unsigned int tex_id = m_icons_texture.get_id(); | ||||
|     int tex_width = m_icons_texture.get_width(); | ||||
|     int tex_height = m_icons_texture.get_height(); | ||||
| #else | ||||
|     unsigned int tex_id = m_icons_texture.texture.get_id(); | ||||
|     int tex_width = m_icons_texture.texture.get_width(); | ||||
|     int tex_height = m_icons_texture.texture.get_height(); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = inv_zoom * m_layout.scale; | ||||
| #else | ||||
|     float factor = inv_zoom * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -1121,10 +995,8 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | |||
|     left += scaled_border; | ||||
|     top -= scaled_border; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // renders icons
 | ||||
|     for (const GLToolbarItem* item : m_items) | ||||
|  | @ -1136,11 +1008,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | |||
|             left += separator_stride; | ||||
|         else | ||||
|         { | ||||
| #if ENABLE_SVG_ICONS | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); | ||||
| #else | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|             left += icon_stride; | ||||
|         } | ||||
|     } | ||||
|  | @ -1148,34 +1016,15 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const | |||
| 
 | ||||
| void GLToolbar::render_vertical(const GLCanvas3D& parent) const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     unsigned int tex_id = m_icons_texture.get_id(); | ||||
|     int tex_width = m_icons_texture.get_width(); | ||||
|     int tex_height = m_icons_texture.get_height(); | ||||
| #else | ||||
|     unsigned int tex_id = m_icons_texture.texture.get_id(); | ||||
|     int tex_width = m_icons_texture.texture.get_width(); | ||||
|     int tex_height = m_icons_texture.texture.get_height(); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     float zoom = (float)parent.get_camera().get_zoom(); | ||||
|     float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float factor = inv_zoom * m_layout.scale; | ||||
| #else | ||||
|     float factor = inv_zoom * m_layout.icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_layout.icons_size * factor; | ||||
| #else | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_layout.icons_scale * factor; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float scaled_separator_size = m_layout.separator_size * factor; | ||||
|     float scaled_gap_size = m_layout.gap_size * factor; | ||||
|     float scaled_border = m_layout.border * factor; | ||||
|  | @ -1276,10 +1125,8 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const | |||
|     left += scaled_border; | ||||
|     top -= scaled_border; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if ((tex_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // renders icons
 | ||||
|     for (const GLToolbarItem* item : m_items) | ||||
|  | @ -1291,17 +1138,12 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const | |||
|             top -= separator_stride; | ||||
|         else | ||||
|         { | ||||
| #if ENABLE_SVG_ICONS | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, (unsigned int)(m_layout.icons_size * m_layout.scale)); | ||||
| #else | ||||
|             item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_width, (unsigned int)tex_height, m_icons_texture.metadata.icon_size); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|             top -= icon_stride; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| bool GLToolbar::generate_icons_texture() const | ||||
| { | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|  | @ -1337,7 +1179,6 @@ bool GLToolbar::generate_icons_texture() const | |||
| 
 | ||||
|     return res; | ||||
| } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| bool GLToolbar::update_items_visibility() | ||||
| { | ||||
|  |  | |||
|  | @ -58,9 +58,7 @@ public: | |||
|     struct Data | ||||
|     { | ||||
|         std::string name; | ||||
| #if ENABLE_SVG_ICONS | ||||
|         std::string icon_filename; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|         std::string tooltip; | ||||
|         unsigned int sprite_id; | ||||
|         bool is_toggable; | ||||
|  | @ -88,9 +86,7 @@ public: | |||
|     void set_state(EState state) { m_state = state; } | ||||
| 
 | ||||
|     const std::string& get_name() const { return m_data.name; } | ||||
| #if ENABLE_SVG_ICONS | ||||
|     const std::string& get_icon_filename() const { return m_data.icon_filename; } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     const std::string& get_tooltip() const { return m_data.tooltip; } | ||||
| 
 | ||||
|     void do_action() { m_data.action_callback(); } | ||||
|  | @ -118,27 +114,6 @@ private: | |||
|     friend class GLToolbar; | ||||
| }; | ||||
| 
 | ||||
| #if !ENABLE_SVG_ICONS | ||||
| // items icon textures are assumed to be square and all with the same size in pixels, no internal check is done
 | ||||
| // icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState
 | ||||
| // from left to right
 | ||||
| struct ItemsIconsTexture | ||||
| { | ||||
|     struct Metadata | ||||
|     { | ||||
|         // path of the file containing the icons' texture
 | ||||
|         std::string filename; | ||||
|         // size of the square icons, in pixels
 | ||||
|         unsigned int icon_size; | ||||
| 
 | ||||
|         Metadata(); | ||||
|     }; | ||||
| 
 | ||||
|     GLTexture texture; | ||||
|     Metadata metadata; | ||||
| }; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| struct BackgroundTexture | ||||
| { | ||||
|     struct Metadata | ||||
|  | @ -164,9 +139,7 @@ struct BackgroundTexture | |||
| class GLToolbar | ||||
| { | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     static const float Default_Icons_Size; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     enum EType : unsigned char | ||||
|     { | ||||
|  | @ -201,12 +174,8 @@ public: | |||
|         float border; | ||||
|         float separator_size; | ||||
|         float gap_size; | ||||
| #if ENABLE_SVG_ICONS | ||||
|         float icons_size; | ||||
|         float scale; | ||||
| #else | ||||
|         float icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|         float width; | ||||
|         float height; | ||||
|  | @ -219,16 +188,10 @@ private: | |||
|     typedef std::vector<GLToolbarItem*> ItemsList; | ||||
| 
 | ||||
|     EType m_type; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     std::string m_name; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     bool m_enabled; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     mutable GLTexture m_icons_texture; | ||||
|     mutable bool m_icons_texture_dirty; | ||||
| #else | ||||
|     ItemsIconsTexture m_icons_texture; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     BackgroundTexture m_background_texture; | ||||
|     mutable Layout m_layout; | ||||
| 
 | ||||
|  | @ -251,18 +214,10 @@ private: | |||
|     std::string m_tooltip; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLToolbar(EType type, const std::string& name); | ||||
| #else | ||||
|     explicit GLToolbar(EType type); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     ~GLToolbar(); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     bool init(const BackgroundTexture::Metadata& background_texture); | ||||
| #else | ||||
|     bool init(const ItemsIconsTexture::Metadata& icons_texture, const BackgroundTexture::Metadata& background_texture); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Layout::EType get_layout_type() const; | ||||
|     void set_layout_type(Layout::EType type); | ||||
|  | @ -273,12 +228,8 @@ public: | |||
|     void set_border(float border); | ||||
|     void set_separator_size(float size); | ||||
|     void set_gap_size(float size); | ||||
| #if ENABLE_SVG_ICONS | ||||
|     void set_icons_size(float size); | ||||
|     void set_scale(float scale); | ||||
| #else | ||||
|     void set_icons_scale(float scale); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     bool is_enabled() const; | ||||
|     void set_enabled(bool enable); | ||||
|  | @ -297,7 +248,6 @@ public: | |||
| 
 | ||||
|     const std::string& get_tooltip() const { return m_tooltip; } | ||||
| 
 | ||||
| 
 | ||||
|     // returns true if any item changed its state
 | ||||
|     bool update_items_state(); | ||||
| 
 | ||||
|  | @ -324,9 +274,7 @@ private: | |||
|     void render_horizontal(const GLCanvas3D& parent) const; | ||||
|     void render_vertical(const GLCanvas3D& parent) const; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     bool generate_icons_texture() const; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     // returns true if any item changed its state
 | ||||
|     bool update_items_visibility(); | ||||
|  |  | |||
|  | @ -17,6 +17,28 @@ namespace Slic3r | |||
| namespace GUI | ||||
| { | ||||
| 
 | ||||
| 
 | ||||
| // Helper function to be used by drop to bed button. Returns lowest point of this
 | ||||
| // volume in world coordinate system.
 | ||||
| static double get_volume_min_z(const GLVolume* volume) | ||||
| { | ||||
|     const Transform3f& world_matrix = volume->world_matrix().cast<float>(); | ||||
| 
 | ||||
|     // need to get the ModelVolume pointer
 | ||||
|     const ModelObject* mo = wxGetApp().model_objects()->at(volume->composite_id.object_id); | ||||
|     const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; | ||||
|     const TriangleMesh& hull = mv->get_convex_hull(); | ||||
| 
 | ||||
|     float min_z = std::numeric_limits<float>::max(); | ||||
|     for (const stl_facet& facet : hull.stl.facet_start) { | ||||
|         for (int i = 0; i < 3; ++ i) | ||||
|             min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i])); | ||||
|     } | ||||
|     return min_z; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| static wxBitmapComboBox* create_word_local_combo(wxWindow *parent) | ||||
| { | ||||
|     wxSize size(15 * wxGetApp().em_unit(), -1); | ||||
|  | @ -185,7 +207,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|         unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2
 | ||||
| 
 | ||||
|         // We will add a button to toggle mirroring to each axis:
 | ||||
|         auto mirror_button = [=](wxWindow* parent) { | ||||
|         auto mirror_button = [this, mirror_btn_width, axis_idx, &label](wxWindow* parent) { | ||||
|             wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width); | ||||
|             auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); | ||||
|             btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label)); | ||||
|  | @ -195,7 +217,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|             auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|             sizer->Add(btn); | ||||
| 
 | ||||
|             btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|             btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent &e) { | ||||
|                 Axis axis = (Axis)(axis_idx + X); | ||||
|                 if (m_mirror_buttons[axis_idx].second == mbHidden) | ||||
|                     return; | ||||
|  | @ -258,13 +280,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|                 return btn; | ||||
|             }; | ||||
|             // Add reset scale button
 | ||||
|             auto reset_scale_button = [=](wxWindow* parent) { | ||||
|             auto reset_scale_button = [this](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); | ||||
|                 btn->SetToolTip(_(L("Reset scale"))); | ||||
|                 m_reset_scale_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                 btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { | ||||
|                     change_scale_value(0, 100.); | ||||
|                     change_scale_value(1, 100.); | ||||
|                     change_scale_value(2, 100.); | ||||
|  | @ -275,13 +297,13 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|         } | ||||
|         else if (option_name == "Rotation") { | ||||
|             // Add reset rotation button
 | ||||
|             auto reset_rotation_button = [=](wxWindow* parent) { | ||||
|             auto reset_rotation_button = [this](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); | ||||
|                 btn->SetToolTip(_(L("Reset rotation"))); | ||||
|                 m_reset_rotation_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                 btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e) { | ||||
|                     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|                     Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|  | @ -310,6 +332,34 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : | |||
|             }; | ||||
|             line.append_widget(reset_rotation_button); | ||||
|         } | ||||
|         else if (option_name == "Position") { | ||||
|             // Add drop to bed button
 | ||||
|             auto drop_to_bed_button = [=](wxWindow* parent) { | ||||
|                 auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed.png")); | ||||
|                 btn->SetToolTip(_(L("Drop to bed"))); | ||||
|                 m_drop_to_bed_button = btn; | ||||
|                 auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|                 sizer->Add(btn, wxBU_EXACTFIT); | ||||
|                 btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) { | ||||
|                     // ???
 | ||||
|                     GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); | ||||
|                     Selection& selection = canvas->get_selection(); | ||||
| 
 | ||||
|                     if (selection.is_single_volume() || selection.is_single_modifier()) { | ||||
|                         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
| 
 | ||||
|                         const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); | ||||
|                         Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); | ||||
| 
 | ||||
|                         change_position_value(0, diff.x()); | ||||
|                         change_position_value(1, diff.y()); | ||||
|                         change_position_value(2, diff.z()); | ||||
|                     } | ||||
|                 }); | ||||
|             return sizer; | ||||
|             }; | ||||
|             line.append_widget(drop_to_bed_button); | ||||
|         } | ||||
|         // Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment
 | ||||
|         else if (option_name == "Size") { | ||||
|             line.near_label_widget = [this](wxWindow* parent) { | ||||
|  | @ -536,11 +586,13 @@ void ObjectManipulation::update_reset_buttons_visibility() | |||
| 
 | ||||
|     bool show_rotation = false; | ||||
|     bool show_scale = false; | ||||
|     bool show_drop_to_bed = false; | ||||
| 
 | ||||
|     if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) { | ||||
|         const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); | ||||
|         Vec3d rotation; | ||||
|         Vec3d scale; | ||||
|         double min_z = 0.; | ||||
| 
 | ||||
|         if (selection.is_single_full_instance()) { | ||||
|             rotation = volume->get_instance_rotation(); | ||||
|  | @ -549,14 +601,17 @@ void ObjectManipulation::update_reset_buttons_visibility() | |||
|         else { | ||||
|             rotation = volume->get_volume_rotation(); | ||||
|             scale = volume->get_volume_scaling_factor(); | ||||
|             min_z = get_volume_min_z(volume); | ||||
|         } | ||||
|         show_rotation = !rotation.isApprox(Vec3d::Zero()); | ||||
|         show_scale = !scale.isApprox(Vec3d::Ones()); | ||||
|         show_drop_to_bed = (std::abs(min_z) > EPSILON); | ||||
|     } | ||||
| 
 | ||||
|     wxGetApp().CallAfter([this, show_rotation, show_scale]{ | ||||
|     wxGetApp().CallAfter([this, show_rotation, show_scale, show_drop_to_bed]{ | ||||
|         m_reset_rotation_button->Show(show_rotation); | ||||
|         m_reset_scale_button->Show(show_scale); | ||||
|         m_drop_to_bed_button->Show(show_drop_to_bed); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
|  | @ -869,6 +924,7 @@ void ObjectManipulation::msw_rescale() | |||
|     m_mirror_bitmap_hidden.msw_rescale(); | ||||
|     m_reset_scale_button->msw_rescale(); | ||||
|     m_reset_rotation_button->msw_rescale(); | ||||
|     m_drop_to_bed_button->msw_rescale(); | ||||
| 
 | ||||
|     get_og()->msw_rescale(); | ||||
| } | ||||
|  |  | |||
|  | @ -56,6 +56,7 @@ class ObjectManipulation : public OG_Settings | |||
|     // Non-owning pointers to the reset buttons, so we can hide and show them.
 | ||||
|     ScalableButton* m_reset_scale_button = nullptr; | ||||
|     ScalableButton* m_reset_rotation_button = nullptr; | ||||
|     ScalableButton* m_drop_to_bed_button = nullptr; | ||||
| 
 | ||||
|     // Mirroring buttons and their current state
 | ||||
|     enum MirrorButtonState { | ||||
|  |  | |||
|  | @ -132,18 +132,12 @@ void GLGizmoBase::Grabber::render_face(float half_size) const | |||
|     glsafe(::glEnd()); | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
| #else | ||||
| GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     : m_parent(parent) | ||||
|     , m_group_id(-1) | ||||
|     , m_state(Off) | ||||
|     , m_shortcut_key(0) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_icon_filename(icon_filename) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_sprite_id(sprite_id) | ||||
|     , m_hover_id(-1) | ||||
|     , m_dragging(false) | ||||
|  |  | |||
|  | @ -89,9 +89,7 @@ protected: | |||
|     int m_group_id; | ||||
|     EState m_state; | ||||
|     int m_shortcut_key; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     std::string m_icon_filename; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     unsigned int m_sprite_id; | ||||
|     int m_hover_id; | ||||
|     bool m_dragging; | ||||
|  | @ -102,11 +100,7 @@ protected: | |||
|     ImGuiWrapper* m_imgui; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoBase(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     virtual ~GLGizmoBase() {} | ||||
| 
 | ||||
|     bool init() { return on_init(); } | ||||
|  | @ -122,9 +116,7 @@ public: | |||
|     int get_shortcut_key() const { return m_shortcut_key; } | ||||
|     void set_shortcut_key(int key) { m_shortcut_key = key; } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     const std::string& get_icon_filename() const { return m_icon_filename; } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     bool is_activable(const Selection& selection) const { return on_is_activable(selection); } | ||||
|     bool is_selectable() const { return on_is_selectable(); } | ||||
|  |  | |||
|  | @ -19,13 +19,8 @@ const double GLGizmoCut::Offset = 10.0; | |||
| const double GLGizmoCut::Margin = 20.0; | ||||
| const std::array<float, 3> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0 }; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
| #else | ||||
| GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_cut_z(0.0) | ||||
|     , m_max_z(0.0) | ||||
|     , m_keep_upper(true) | ||||
|  |  | |||
|  | @ -23,11 +23,7 @@ class GLGizmoCut : public GLGizmoBase | |||
|     bool m_rotate_lower; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoCut(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| protected: | ||||
|     virtual bool on_init(); | ||||
|  |  | |||
|  | @ -9,13 +9,8 @@ namespace Slic3r { | |||
| namespace GUI { | ||||
| 
 | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
| #else | ||||
| GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_normal(Vec3d::Zero()) | ||||
|     , m_starting_center(Vec3d::Zero()) | ||||
| { | ||||
|  |  | |||
|  | @ -37,11 +37,7 @@ private: | |||
|     bool is_plane_update_necessary() const; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoFlatten(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     void set_flattening_data(const ModelObject* model_object); | ||||
|     Vec3d get_flattening_normal() const; | ||||
|  |  | |||
|  | @ -10,13 +10,8 @@ namespace GUI { | |||
| 
 | ||||
| const double GLGizmoMove3D::Offset = 10.0; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
| #else | ||||
| GLGizmoMove3D::GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_displacement(Vec3d::Zero()) | ||||
|     , m_snap_step(1.0) | ||||
|     , m_starting_drag_position(Vec3d::Zero()) | ||||
|  |  | |||
|  | @ -22,11 +22,7 @@ class GLGizmoMove3D : public GLGizmoBase | |||
|     GLUquadricObj* m_quadric; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoMove3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoMove3D(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     virtual ~GLGizmoMove3D(); | ||||
| 
 | ||||
|     double get_snap_step(double step) const { return m_snap_step; } | ||||
|  |  | |||
|  | @ -19,11 +19,7 @@ const unsigned int GLGizmoRotate::SnapRegionsCount = 8; | |||
| const float GLGizmoRotate::GrabberOffset = 0.15f; // in percent of radius
 | ||||
| 
 | ||||
| GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     : GLGizmoBase(parent, "", -1) | ||||
| #else | ||||
|     : GLGizmoBase(parent, -1) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_axis(axis) | ||||
|     , m_angle(0.0) | ||||
|     , m_quadric(nullptr) | ||||
|  | @ -40,11 +36,7 @@ GLGizmoRotate::GLGizmoRotate(GLCanvas3D& parent, GLGizmoRotate::Axis axis) | |||
| } | ||||
| 
 | ||||
| GLGizmoRotate::GLGizmoRotate(const GLGizmoRotate& other) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     : GLGizmoBase(other.m_parent, other.m_icon_filename, other.m_sprite_id) | ||||
| #else | ||||
|     : GLGizmoBase(other.m_parent, other.m_sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_axis(other.m_axis) | ||||
|     , m_angle(other.m_angle) | ||||
|     , m_quadric(nullptr) | ||||
|  | @ -417,13 +409,8 @@ Vec3d GLGizmoRotate::mouse_position_in_local_plane(const Linef3& mouse_ray, cons | |||
|     return transform(mouse_ray, m).intersect_plane(0.0); | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
| #else | ||||
| GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| { | ||||
|     m_gizmos.emplace_back(parent, GLGizmoRotate::X); | ||||
|     m_gizmos.emplace_back(parent, GLGizmoRotate::Y); | ||||
|  |  | |||
|  | @ -76,11 +76,7 @@ class GLGizmoRotate3D : public GLGizmoBase | |||
|     std::vector<GLGizmoRotate> m_gizmos; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoRotate3D(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); } | ||||
|     void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); } | ||||
|  |  | |||
|  | @ -13,13 +13,8 @@ namespace GUI { | |||
| 
 | ||||
| const float GLGizmoScale3D::Offset = 5.0f; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
| #else | ||||
| GLGizmoScale3D::GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_scale(Vec3d::Ones()) | ||||
|     , m_offset(Vec3d::Zero()) | ||||
|     , m_snap_step(0.05) | ||||
|  |  | |||
|  | @ -32,11 +32,7 @@ class GLGizmoScale3D : public GLGizmoBase | |||
|     StartingData m_starting; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoScale3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoScale3D(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     double get_snap_step(double step) const { return m_snap_step; } | ||||
|     void set_snap_step(double step) { m_snap_step = step; } | ||||
|  |  | |||
|  | @ -19,13 +19,8 @@ | |||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, icon_filename, sprite_id) | ||||
| #else | ||||
| GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id) | ||||
|     : GLGizmoBase(parent, sprite_id) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_quadric(nullptr) | ||||
|     , m_its(nullptr) | ||||
| { | ||||
|  |  | |||
|  | @ -58,11 +58,7 @@ private: | |||
|     }; | ||||
| 
 | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); | ||||
| #else | ||||
|     GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_id); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     virtual ~GLGizmoSlaSupports(); | ||||
|     void set_sla_support_data(ModelObject* model_object, const Selection& selection); | ||||
|     bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); | ||||
|  |  | |||
|  | @ -12,17 +12,12 @@ | |||
| namespace Slic3r { | ||||
| namespace GUI { | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     const float GLGizmosManager::Default_Icons_Size = 64; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| const float GLGizmosManager::Default_Icons_Size = 64; | ||||
| 
 | ||||
| GLGizmosManager::GLGizmosManager() | ||||
|     : m_enabled(false) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_icons_texture_dirty(true) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_current(Undefined) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , m_overlay_icons_size(Default_Icons_Size) | ||||
|     , m_overlay_scale(1.0f) | ||||
|     , m_overlay_border(5.0f) | ||||
|  | @ -30,11 +25,6 @@ GLGizmosManager::GLGizmosManager() | |||
|     , m_tooltip("") | ||||
| { | ||||
| } | ||||
| #else | ||||
| { | ||||
|     set_overlay_scale(1.0); | ||||
| } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| GLGizmosManager::~GLGizmosManager() | ||||
| { | ||||
|  | @ -43,20 +33,6 @@ GLGizmosManager::~GLGizmosManager() | |||
| 
 | ||||
| bool GLGizmosManager::init(GLCanvas3D& parent) | ||||
| { | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     m_icons_texture.metadata.filename = "gizmos.png"; | ||||
|     m_icons_texture.metadata.icon_size = 64; | ||||
| 
 | ||||
|     if (!m_icons_texture.metadata.filename.empty()) | ||||
|     { | ||||
|         if (!m_icons_texture.texture.load_from_file(resources_dir() + "/icons/" + m_icons_texture.metadata.filename, false, true)) | ||||
|         { | ||||
|             reset(); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     m_background_texture.metadata.filename = "toolbar_background.png"; | ||||
|     m_background_texture.metadata.left = 16; | ||||
|     m_background_texture.metadata.top = 16; | ||||
|  | @ -72,11 +48,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     GLGizmoBase* gizmo = new GLGizmoMove3D(parent, "move.svg", 0); | ||||
| #else | ||||
|     GLGizmoBase* gizmo = new GLGizmoMove3D(parent, 0); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     if (gizmo == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|  | @ -85,11 +57,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
| 
 | ||||
|     m_gizmos.insert(GizmosMap::value_type(Move, gizmo)); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     gizmo = new GLGizmoScale3D(parent, "scale.svg", 1); | ||||
| #else | ||||
|     gizmo = new GLGizmoScale3D(parent, 1); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     if (gizmo == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|  | @ -98,11 +66,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
| 
 | ||||
|     m_gizmos.insert(GizmosMap::value_type(Scale, gizmo)); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     gizmo = new GLGizmoRotate3D(parent, "rotate.svg", 2); | ||||
| #else | ||||
|     gizmo = new GLGizmoRotate3D(parent, 2); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     if (gizmo == nullptr) | ||||
|     { | ||||
|         reset(); | ||||
|  | @ -117,11 +81,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
| 
 | ||||
|     m_gizmos.insert(GizmosMap::value_type(Rotate, gizmo)); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     gizmo = new GLGizmoFlatten(parent, "place.svg", 3); | ||||
| #else | ||||
|     gizmo = new GLGizmoFlatten(parent, 3); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     if (gizmo == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|  | @ -132,11 +92,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
| 
 | ||||
|     m_gizmos.insert(GizmosMap::value_type(Flatten, gizmo)); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     gizmo = new GLGizmoCut(parent, "cut.svg", 4); | ||||
| #else | ||||
|     gizmo = new GLGizmoCut(parent, 4); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     if (gizmo == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|  | @ -147,11 +103,7 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
| 
 | ||||
|     m_gizmos.insert(GizmosMap::value_type(Cut, gizmo)); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     gizmo = new GLGizmoSlaSupports(parent, "sla_supports.svg", 5); | ||||
| #else | ||||
|     gizmo = new GLGizmoSlaSupports(parent, 5); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     if (gizmo == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|  | @ -165,7 +117,6 @@ bool GLGizmosManager::init(GLCanvas3D& parent) | |||
|     return true; | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| void GLGizmosManager::set_overlay_icon_size(float size) | ||||
| { | ||||
|     if (m_overlay_icons_size != size) | ||||
|  | @ -174,21 +125,14 @@ void GLGizmosManager::set_overlay_icon_size(float size) | |||
|         m_icons_texture_dirty = true; | ||||
|     } | ||||
| } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| void GLGizmosManager::set_overlay_scale(float scale) | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (m_overlay_scale != scale) | ||||
|     { | ||||
|         m_overlay_scale = scale; | ||||
|         m_icons_texture_dirty = true; | ||||
|     } | ||||
| #else | ||||
|     m_overlay_icons_scale = scale; | ||||
|     m_overlay_border = 5.0f * scale; | ||||
|     m_overlay_gap_y = 5.0f * scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| } | ||||
| 
 | ||||
| void GLGizmosManager::refresh_on_off_state(const Selection& selection) | ||||
|  | @ -526,10 +470,8 @@ void GLGizmosManager::render_overlay(const GLCanvas3D& canvas, const Selection& | |||
|     if (!m_enabled) | ||||
|         return; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (m_icons_texture_dirty) | ||||
|         generate_icons_texture(); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     do_render_overlay(canvas, selection); | ||||
| } | ||||
|  | @ -935,11 +877,7 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio | |||
| 
 | ||||
|     float height = get_total_overlay_height(); | ||||
|     float width = get_total_overlay_width(); | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale * inv_zoom; | ||||
| #else | ||||
|     float scaled_border = m_overlay_border * inv_zoom; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     float top_x = (-0.5f * cnv_w) * inv_zoom; | ||||
|     float top_y = (0.5f * height) * inv_zoom; | ||||
|  | @ -1015,7 +953,6 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio | |||
|         } | ||||
|     } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     top_x += scaled_border; | ||||
|     top_y -= scaled_border; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale * inv_zoom; | ||||
|  | @ -1027,21 +964,9 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio | |||
|     unsigned int tex_height = m_icons_texture.get_height(); | ||||
|     float inv_tex_width = (tex_width != 0) ? 1.0f / (float)tex_width : 0.0f; | ||||
|     float inv_tex_height = (tex_height != 0) ? 1.0f / (float)tex_height : 0.0f; | ||||
| #else | ||||
|     top_x += m_overlay_border * inv_zoom; | ||||
|     top_y -= m_overlay_border * inv_zoom; | ||||
|     float scaled_gap_y = m_overlay_gap_y * inv_zoom; | ||||
| 
 | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale * inv_zoom; | ||||
|     unsigned int icons_texture_id = m_icons_texture.texture.get_id(); | ||||
|     unsigned int texture_size = m_icons_texture.texture.get_width(); | ||||
|     float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if ((icons_texture_id == 0) || (tex_width <= 0) || (tex_height <= 0)) | ||||
|         return; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) | ||||
|     { | ||||
|  | @ -1051,78 +976,44 @@ void GLGizmosManager::do_render_overlay(const GLCanvas3D& canvas, const Selectio | |||
|         unsigned int sprite_id = it->second->get_sprite_id(); | ||||
|         GLGizmoBase::EState state = it->second->get_state(); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         float u_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_width; | ||||
|         float v_icon_size = m_overlay_icons_size * m_overlay_scale * inv_tex_height; | ||||
|         float v_top = sprite_id * v_icon_size; | ||||
|         float u_left = state * u_icon_size; | ||||
|         float v_bottom = v_top + v_icon_size; | ||||
|         float u_right = u_left + u_icon_size; | ||||
| #else | ||||
|         float uv_icon_size = (float)m_icons_texture.metadata.icon_size * inv_texture_size; | ||||
|         float v_top = sprite_id * uv_icon_size; | ||||
|         float u_left = state * uv_icon_size; | ||||
|         float v_bottom = v_top + uv_icon_size; | ||||
|         float u_right = u_left + uv_icon_size; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|         GLTexture::render_sub_texture(icons_texture_id, top_x, top_x + scaled_icons_size, top_y - scaled_icons_size, top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); | ||||
|         if (it->second->get_state() == GLGizmoBase::On) { | ||||
|             float toolbar_top = (float)cnv_h - canvas.get_view_toolbar_height(); | ||||
| #if ENABLE_SVG_ICONS | ||||
|             it->second->render_input_window(width, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); | ||||
| #else | ||||
|             it->second->render_input_window(2.0f * m_overlay_border + scaled_icons_size * zoom, 0.5f * cnv_h - top_y * zoom, toolbar_top, selection); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|         } | ||||
| #if ENABLE_SVG_ICONS | ||||
|         top_y -= scaled_stride_y; | ||||
| #else | ||||
|         top_y -= (scaled_icons_size + scaled_gap_y); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| float GLGizmosManager::get_total_overlay_height() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; | ||||
|     float scaled_stride_y = scaled_icons_size + scaled_gap_y; | ||||
|     float height = 2.0f * scaled_border; | ||||
| #else | ||||
|     float height = 2.0f * m_overlay_border; | ||||
| 
 | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) | ||||
|     { | ||||
|         if ((it->second == nullptr) || !it->second->is_selectable()) | ||||
|             continue; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         height += scaled_stride_y; | ||||
| #else | ||||
|         height += (scaled_icons_size + m_overlay_gap_y); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     return height - scaled_gap_y; | ||||
| #else | ||||
|     return height - m_overlay_gap_y; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| } | ||||
| 
 | ||||
| float GLGizmosManager::get_total_overlay_width() const | ||||
| { | ||||
| #if ENABLE_SVG_ICONS | ||||
|     return (2.0f * m_overlay_border + m_overlay_icons_size) * m_overlay_scale; | ||||
| #else | ||||
|     return (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale + 2.0f * m_overlay_border; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| } | ||||
| 
 | ||||
| GLGizmoBase* GLGizmosManager::get_current() const | ||||
|  | @ -1131,7 +1022,6 @@ GLGizmoBase* GLGizmosManager::get_current() const | |||
|     return (it != m_gizmos.end()) ? it->second : nullptr; | ||||
| } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
| bool GLGizmosManager::generate_icons_texture() const | ||||
| { | ||||
|     std::string path = resources_dir() + "/icons/"; | ||||
|  | @ -1157,7 +1047,6 @@ bool GLGizmosManager::generate_icons_texture() const | |||
| 
 | ||||
|     return res; | ||||
| } | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
| void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection) | ||||
| { | ||||
|  | @ -1167,27 +1056,18 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& | |||
|     float cnv_h = (float)canvas.get_canvas_size().get_height(); | ||||
|     float height = get_total_overlay_height(); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; | ||||
|     float scaled_stride_y = scaled_icons_size + scaled_gap_y; | ||||
|     float top_y = 0.5f * (cnv_h - height) + scaled_border; | ||||
| #else | ||||
|     float top_y = 0.5f * (cnv_h - height) + m_overlay_border; | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) | ||||
|     { | ||||
|         if ((it->second == nullptr) || !it->second->is_selectable()) | ||||
|             continue; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); | ||||
| #else | ||||
|         bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|         if (it->second->is_activable(selection) && inside) | ||||
|         { | ||||
|             if ((it->second->get_state() == GLGizmoBase::On)) | ||||
|  | @ -1204,11 +1084,7 @@ void GLGizmosManager::update_on_off_state(const GLCanvas3D& canvas, const Vec2d& | |||
|         else | ||||
|             it->second->set_state(GLGizmoBase::Off); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         top_y += scaled_stride_y; | ||||
| #else | ||||
|         top_y += (scaled_icons_size + m_overlay_gap_y); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     } | ||||
| 
 | ||||
|     GizmosMap::iterator it = m_gizmos.find(m_current); | ||||
|  | @ -1227,38 +1103,25 @@ std::string GLGizmosManager::update_hover_state(const GLCanvas3D& canvas, const | |||
| 
 | ||||
|     float cnv_h = (float)canvas.get_canvas_size().get_height(); | ||||
|     float height = get_total_overlay_height(); | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; | ||||
|     float scaled_stride_y = scaled_icons_size + scaled_gap_y; | ||||
|     float top_y = 0.5f * (cnv_h - height) + scaled_border; | ||||
| #else | ||||
|     float top_y = 0.5f * (cnv_h - height) + m_overlay_border; | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) | ||||
|     { | ||||
|         if ((it->second == nullptr) || !it->second->is_selectable()) | ||||
|             continue; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         bool inside = (scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); | ||||
| #else | ||||
|         bool inside = (m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|         if (inside) | ||||
|             name = it->second->get_name(); | ||||
| 
 | ||||
|         if (it->second->is_activable(selection) && (it->second->get_state() != GLGizmoBase::On)) | ||||
|             it->second->set_state(inside ? GLGizmoBase::Hover : GLGizmoBase::Off); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         top_y += scaled_stride_y; | ||||
| #else | ||||
|         top_y += (scaled_icons_size + m_overlay_gap_y); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     } | ||||
| 
 | ||||
|     return name; | ||||
|  | @ -1272,34 +1135,21 @@ bool GLGizmosManager::overlay_contains_mouse(const GLCanvas3D& canvas, const Vec | |||
|     float cnv_h = (float)canvas.get_canvas_size().get_height(); | ||||
|     float height = get_total_overlay_height(); | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float scaled_icons_size = m_overlay_icons_size * m_overlay_scale; | ||||
|     float scaled_border = m_overlay_border * m_overlay_scale; | ||||
|     float scaled_gap_y = m_overlay_gap_y * m_overlay_scale; | ||||
|     float scaled_stride_y = scaled_icons_size + scaled_gap_y; | ||||
|     float top_y = 0.5f * (cnv_h - height) + scaled_border; | ||||
| #else | ||||
|     float top_y = 0.5f * (cnv_h - height) + m_overlay_border; | ||||
|     float scaled_icons_size = (float)m_icons_texture.metadata.icon_size * m_overlay_icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     for (GizmosMap::const_iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) | ||||
|     { | ||||
|         if ((it->second == nullptr) || !it->second->is_selectable()) | ||||
|             continue; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         if ((scaled_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= scaled_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) | ||||
| #else | ||||
|         if ((m_overlay_border <= (float)mouse_pos(0)) && ((float)mouse_pos(0) <= m_overlay_border + scaled_icons_size) && (top_y <= (float)mouse_pos(1)) && ((float)mouse_pos(1) <= top_y + scaled_icons_size)) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|             return true; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|         top_y += scaled_stride_y; | ||||
| #else | ||||
|         top_y += (scaled_icons_size + m_overlay_gap_y); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     } | ||||
| 
 | ||||
|     return false; | ||||
|  |  | |||
|  | @ -46,9 +46,7 @@ public: | |||
| class GLGizmosManager | ||||
| { | ||||
| public: | ||||
| #if ENABLE_SVG_ICONS | ||||
|     static const float Default_Icons_Size; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     enum EType : unsigned char | ||||
|     { | ||||
|  | @ -66,21 +64,13 @@ private: | |||
|     bool m_enabled; | ||||
|     typedef std::map<EType, GLGizmoBase*> GizmosMap; | ||||
|     GizmosMap m_gizmos; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     mutable GLTexture m_icons_texture; | ||||
|     mutable bool m_icons_texture_dirty; | ||||
| #else | ||||
|     ItemsIconsTexture m_icons_texture; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     BackgroundTexture m_background_texture; | ||||
|     EType m_current; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     float m_overlay_icons_size; | ||||
|     float m_overlay_scale; | ||||
| #else | ||||
|     float m_overlay_icons_scale; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     float m_overlay_border; | ||||
|     float m_overlay_gap_y; | ||||
| 
 | ||||
|  | @ -109,9 +99,7 @@ public: | |||
|     bool is_enabled() const { return m_enabled; } | ||||
|     void set_enabled(bool enable) { m_enabled = enable; } | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     void set_overlay_icon_size(float size); | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     void set_overlay_scale(float scale); | ||||
| 
 | ||||
|     void refresh_on_off_state(const Selection& selection); | ||||
|  | @ -173,9 +161,7 @@ private: | |||
| 
 | ||||
|     GLGizmoBase* get_current() const; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     bool generate_icons_texture() const; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     void update_on_off_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos, const Selection& selection); | ||||
|     std::string update_hover_state(const GLCanvas3D& canvas, const Vec2d& mouse_pos); | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ namespace GUI { | |||
| MainFrame::MainFrame() : | ||||
| DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), | ||||
|     m_printhost_queue_dlg(new PrintHostQueueDialog(this)) | ||||
|     , m_recent_projects(9) | ||||
| { | ||||
|     // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened.
 | ||||
|     wxGetApp().update_fonts(this); | ||||
|  | @ -383,6 +384,40 @@ void MainFrame::init_menubar() | |||
|         append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, menu_icon("open"), nullptr, | ||||
|             [this](){return m_plater != nullptr; }, this); | ||||
| 
 | ||||
|         wxMenu* recent_projects_menu = new wxMenu(); | ||||
|         wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _(L("Recent projects")), ""); | ||||
|         m_recent_projects.UseMenu(recent_projects_menu); | ||||
|         Bind(wxEVT_MENU, [this](wxCommandEvent& evt) { | ||||
|             size_t file_id = evt.GetId() - wxID_FILE1; | ||||
|             wxString filename = m_recent_projects.GetHistoryFile(file_id); | ||||
|             if (wxFileExists(filename)) | ||||
|                 m_plater->load_project(filename); | ||||
|             else | ||||
|             { | ||||
|                 wxMessageDialog msg(this, _(L("The selected project is no more available")), _(L("Error"))); | ||||
|                 msg.ShowModal(); | ||||
| 
 | ||||
|                 m_recent_projects.RemoveFileFromHistory(file_id); | ||||
|                 std::vector<std::string> recent_projects; | ||||
|                 size_t count = m_recent_projects.GetCount(); | ||||
|                 for (size_t i = 0; i < count; ++i) | ||||
|                 { | ||||
|                     recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); | ||||
|                 } | ||||
|                 wxGetApp().app_config->set_recent_projects(recent_projects); | ||||
|                 wxGetApp().app_config->save(); | ||||
|             } | ||||
|             }, wxID_FILE1, wxID_FILE9); | ||||
| 
 | ||||
|         std::vector<std::string> recent_projects = wxGetApp().app_config->get_recent_projects(); | ||||
|         for (const std::string& project : recent_projects) | ||||
|         { | ||||
|             m_recent_projects.AddFileToHistory(from_u8(project)); | ||||
|         } | ||||
| 
 | ||||
|         Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); | ||||
| 
 | ||||
|         append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")), | ||||
|             [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, menu_icon("save"), nullptr, | ||||
|             [this](){return m_plater != nullptr && can_save(); }, this); | ||||
|  | @ -1046,6 +1081,23 @@ void MainFrame::on_config_changed(DynamicPrintConfig* config) const | |||
|         m_plater->on_config_change(*config); // propagate config change events to the plater
 | ||||
| } | ||||
| 
 | ||||
| void MainFrame::add_to_recent_projects(const wxString& filename) | ||||
| { | ||||
|     if (wxFileExists(filename)) | ||||
|     { | ||||
|         m_recent_projects.AddFileToHistory(filename); | ||||
|         std::vector<std::string> recent_projects; | ||||
|         size_t count = m_recent_projects.GetCount(); | ||||
|         for (size_t i = 0; i < count; ++i) | ||||
|         { | ||||
|             recent_projects.push_back(into_u8(m_recent_projects.GetHistoryFile(i))); | ||||
|         } | ||||
|         wxGetApp().app_config->set_recent_projects(recent_projects); | ||||
|         wxGetApp().app_config->save(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| //
 | ||||
| // Called after the Preferences dialog is closed and the program settings are saved.
 | ||||
| // Update the UI based on the current preferences.
 | ||||
| void MainFrame::update_ui_from_settings() | ||||
|  |  | |||
|  | @ -6,6 +6,7 @@ | |||
| #include <wx/frame.h> | ||||
| #include <wx/settings.h> | ||||
| #include <wx/string.h> | ||||
| #include <wx/filehistory.h> | ||||
| 
 | ||||
| #include <string> | ||||
| #include <map> | ||||
|  | @ -84,6 +85,8 @@ class MainFrame : public DPIFrame | |||
|     // vector of a MenuBar items changeable in respect to printer technology 
 | ||||
|     std::vector<wxMenuItem*> m_changeable_menu_items; | ||||
| 
 | ||||
|     wxFileHistory m_recent_projects; | ||||
| 
 | ||||
| protected: | ||||
|     virtual void on_dpi_changed(const wxRect &suggested_rect); | ||||
| 
 | ||||
|  | @ -121,6 +124,8 @@ public: | |||
|     // Propagate changed configuration from the Tab to the Platter and save changes to the AppConfig
 | ||||
|     void        on_config_changed(DynamicPrintConfig* cfg) const ; | ||||
| 
 | ||||
|     void        add_to_recent_projects(const wxString& filename); | ||||
| 
 | ||||
|     PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } | ||||
| 
 | ||||
|     Plater*             m_plater { nullptr }; | ||||
|  |  | |||
|  | @ -266,7 +266,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	full_Label/* = n | |||
|             is_sizer_field(field) ? | ||||
|                 v_sizer->Add(field->getSizer(), 0, wxEXPAND) : | ||||
|                 v_sizer->Add(field->getWindow(), 0, wxEXPAND); | ||||
|             return; | ||||
|             break;//return;
 | ||||
|         } | ||||
| 
 | ||||
| 		is_sizer_field(field) ?  | ||||
|  | @ -300,7 +300,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText**	full_Label/* = n | |||
|         { | ||||
|             // extra widget for non-staticbox option group (like for the frequently used parameters on the sidebar) should be wxALIGN_RIGHT
 | ||||
|             const auto v_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|             sizer->Add(v_sizer, 1, wxEXPAND); | ||||
|             sizer->Add(v_sizer, option_set.size() == 1 ? 0 : 1, wxEXPAND); | ||||
|             v_sizer->Add(extra_widget(this->ctrl_parent()), 0, wxALIGN_RIGHT); | ||||
|             return; | ||||
|         } | ||||
|  |  | |||
|  | @ -162,6 +162,12 @@ public: | |||
| 
 | ||||
|     void            clear_fields_except_of(const std::vector<std::string> left_fields); | ||||
| 
 | ||||
|     void            hide_labels() { | ||||
|         label_width = 0; | ||||
|         m_grid_sizer->SetCols(m_grid_sizer->GetEffectiveColsCount()-1); | ||||
|         static_cast<wxFlexGridSizer*>(m_grid_sizer)->AddGrowableCol(!extra_column ? 0 : 1); | ||||
|     } | ||||
| 
 | ||||
| 	OptionsGroup(	wxWindow* _parent, const wxString& title, bool is_tab_opt = false,  | ||||
| 					column_t extra_clmn = nullptr) : | ||||
| 					m_parent(_parent), title(title),  | ||||
|  |  | |||
|  | @ -245,6 +245,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * | |||
|     last_selected(wxNOT_FOUND), | ||||
|     m_em_unit(wxGetApp().em_unit()) | ||||
| { | ||||
|     SetFont(wxGetApp().normal_font()); | ||||
|     Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { | ||||
|         auto selected_item = this->GetSelection(); | ||||
| 
 | ||||
|  | @ -372,24 +373,36 @@ class FreqChangedParams : public OG_Settings | |||
|     wxSizer*        m_sizer {nullptr}; | ||||
| 
 | ||||
|     std::shared_ptr<ConfigOptionsGroup> m_og_sla; | ||||
|     std::vector<ScalableButton*>        m_empty_buttons; | ||||
| public: | ||||
|     FreqChangedParams(wxWindow* parent, const int label_width); | ||||
|     FreqChangedParams(wxWindow* parent); | ||||
|     ~FreqChangedParams() {} | ||||
| 
 | ||||
|     wxButton*       get_wiping_dialog_button() { return m_wiping_dialog_button; } | ||||
|     wxSizer*        get_sizer() override; | ||||
|     ConfigOptionsGroup* get_og(const bool is_fff); | ||||
|     void            Show(const bool is_fff); | ||||
| 
 | ||||
|     void            msw_rescale(); | ||||
| }; | ||||
| 
 | ||||
| FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : | ||||
| void FreqChangedParams::msw_rescale() | ||||
| { | ||||
|     m_og->msw_rescale(); | ||||
|     m_og_sla->msw_rescale(); | ||||
| 
 | ||||
|     for (auto btn: m_empty_buttons) | ||||
|         btn->msw_rescale(); | ||||
| } | ||||
| 
 | ||||
| FreqChangedParams::FreqChangedParams(wxWindow* parent) : | ||||
|     OG_Settings(parent, false) | ||||
| { | ||||
|     DynamicPrintConfig*	config = &wxGetApp().preset_bundle->prints.get_edited_preset().config; | ||||
| 
 | ||||
|     // Frequently changed parameters for FFF_technology
 | ||||
|     m_og->set_config(config); | ||||
|     m_og->label_width = label_width == 0 ? 1 : label_width; | ||||
|     m_og->hide_labels(); | ||||
| 
 | ||||
|     m_og->m_on_change = [config, this](t_config_option_key opt_key, boost::any value) { | ||||
|         Tab* tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT); | ||||
|  | @ -461,6 +474,20 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : | |||
|     Option option = Option(support_def, "support"); | ||||
|     option.opt.full_width = true; | ||||
|     line.append_option(option); | ||||
| 
 | ||||
|     /* Not a best solution, but
 | ||||
|      * Temporary workaround for right border alignment | ||||
|      */ | ||||
|     auto empty_widget = [this] (wxWindow* parent) { | ||||
|         auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString, | ||||
|             wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); | ||||
|         sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, int(0.3 * wxGetApp().em_unit())); | ||||
|         m_empty_buttons.push_back(btn); | ||||
|         return sizer; | ||||
|     }; | ||||
|     line.append_widget(empty_widget); | ||||
| 
 | ||||
|     m_og->append_line(line); | ||||
| 
 | ||||
|      | ||||
|  | @ -469,7 +496,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : | |||
|     option = m_og->get_option("fill_density"); | ||||
|     option.opt.label = L("Infill"); | ||||
|     option.opt.width = 7/*6*/; | ||||
|     option.opt.sidetext = "     "; | ||||
|     option.opt.sidetext = "   "; | ||||
|     line.append_option(option); | ||||
| 
 | ||||
|     m_brim_width = config->opt_float("brim_width"); | ||||
|  | @ -480,13 +507,14 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : | |||
|     def.gui_type = ""; | ||||
|     def.set_default_value(new ConfigOptionBool{ m_brim_width > 0.0 ? true : false }); | ||||
|     option = Option(def, "brim"); | ||||
|     option.opt.sidetext = "     "; | ||||
|     option.opt.sidetext = ""; | ||||
|     line.append_option(option); | ||||
| 
 | ||||
|     auto wiping_dialog_btn = [config, this](wxWindow* parent) { | ||||
|         m_wiping_dialog_button = new wxButton(parent, wxID_ANY, _(L("Purging volumes")) + dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); | ||||
|         m_wiping_dialog_button->SetFont(wxGetApp().normal_font()); | ||||
|         auto sizer = new wxBoxSizer(wxHORIZONTAL); | ||||
|         sizer->Add(m_wiping_dialog_button); | ||||
|         sizer->Add(m_wiping_dialog_button, 0, wxALIGN_CENTER_VERTICAL); | ||||
|         m_wiping_dialog_button->Bind(wxEVT_BUTTON, ([parent](wxCommandEvent& e) | ||||
|         { | ||||
|             auto &config = wxGetApp().preset_bundle->project_config; | ||||
|  | @ -503,6 +531,13 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : | |||
|                 wxPostEvent(parent, SimpleEvent(EVT_SCHEDULE_BACKGROUND_PROCESS, parent)); | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|         auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_transparent.png", wxEmptyString,  | ||||
|                                       wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); | ||||
|         sizer->Add(btn , 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, | ||||
|             int(0.3 * wxGetApp().em_unit())); | ||||
|         m_empty_buttons.push_back(btn); | ||||
| 
 | ||||
|         return sizer; | ||||
|     }; | ||||
|     line.append_widget(wiping_dialog_btn); | ||||
|  | @ -512,9 +547,9 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : | |||
| 
 | ||||
|     // Frequently changed parameters for SLA_technology
 | ||||
|     m_og_sla = std::make_shared<ConfigOptionsGroup>(parent, ""); | ||||
|     m_og_sla->hide_labels(); | ||||
|     DynamicPrintConfig*	config_sla = &wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; | ||||
|     m_og_sla->set_config(config_sla); | ||||
|     m_og_sla->label_width = label_width == 0 ? 1 : label_width; | ||||
| 
 | ||||
|     m_og_sla->m_on_change = [config_sla, this](t_config_option_key opt_key, boost::any value) { | ||||
|         Tab* tab = wxGetApp().get_tab(Preset::TYPE_SLA_PRINT); | ||||
|  | @ -553,6 +588,7 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent, const int label_width) : | |||
|     option = Option(support_def_sla, "support"); | ||||
|     option.opt.full_width = true; | ||||
|     line.append_option(option);  | ||||
|     line.append_widget(empty_widget); | ||||
|     m_og_sla->append_line(line); | ||||
| 
 | ||||
|     line = Line{ "", "" }; | ||||
|  | @ -733,13 +769,12 @@ Sidebar::Sidebar(Plater *parent) | |||
|     init_combo(&p->combo_printer,       _(L("Printer")),            Preset::TYPE_PRINTER,       false); | ||||
| 
 | ||||
|     const int margin_5  = int(0.5*wxGetApp().em_unit());// 5;
 | ||||
|     const int margin_10 = 10;//int(1.5*wxGetApp().em_unit());// 15;
 | ||||
| 
 | ||||
|     p->sizer_params = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     // Frequently changed parameters
 | ||||
|     p->frequently_changed_parameters = new FreqChangedParams(p->scrolled, 0/*label_width*/); | ||||
|     p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, margin_10); | ||||
|     p->frequently_changed_parameters = new FreqChangedParams(p->scrolled); | ||||
|     p->sizer_params->Add(p->frequently_changed_parameters->get_sizer(), 0, wxEXPAND | wxTOP | wxBOTTOM, wxOSX ? 1 : margin_5); | ||||
|      | ||||
|     // Object List
 | ||||
|     p->object_list = new ObjectList(p->scrolled); | ||||
|  | @ -946,9 +981,7 @@ void Sidebar::msw_rescale() | |||
|     // ... then refill them and set min size to correct layout of the sidebar
 | ||||
|     update_all_preset_comboboxes(); | ||||
| 
 | ||||
|     p->frequently_changed_parameters->get_og(true)->msw_rescale(); | ||||
|     p->frequently_changed_parameters->get_og(false)->msw_rescale(); | ||||
| 
 | ||||
|     p->frequently_changed_parameters->msw_rescale(); | ||||
|     p->object_list->msw_rescale(); | ||||
|     p->object_manipulation->msw_rescale(); | ||||
|     p->object_settings->msw_rescale(); | ||||
|  | @ -1132,10 +1165,20 @@ void Sidebar::show_sliced_info_sizer(const bool show) | |||
|                 if (ps.estimated_normal_print_time != "N/A") { | ||||
|                     new_label += wxString::Format("\n    - %s", _(L("normal mode"))); | ||||
|                     info_text += wxString::Format("\n%s", ps.estimated_normal_print_time); | ||||
|                     for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) | ||||
|                     { | ||||
|                         new_label += wxString::Format("\n      - %s%d", _(L("Color ")), i + 1); | ||||
|                         info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); | ||||
|                     } | ||||
|                 } | ||||
|                 if (ps.estimated_silent_print_time != "N/A") { | ||||
|                     new_label += wxString::Format("\n    - %s", _(L("stealth mode"))); | ||||
|                     info_text += wxString::Format("\n%s", ps.estimated_silent_print_time); | ||||
|                     for (int i = (int)ps.estimated_normal_color_print_times.size() - 1; i >= 0; --i) | ||||
|                     { | ||||
|                         new_label += wxString::Format("\n      - %s%d", _(L("Color ")), i + 1); | ||||
|                         info_text += wxString::Format("\n%s", ps.estimated_normal_color_print_times[i]); | ||||
|                     } | ||||
|                 } | ||||
|                 p->sliced_info->SetTextAndShow(siEstimatedTime,  info_text,      new_label); | ||||
|             } | ||||
|  | @ -1693,11 +1736,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|         })) | ||||
|     , sidebar(new Sidebar(q)) | ||||
|     , delayed_scene_refresh(false) | ||||
| #if ENABLE_SVG_ICONS | ||||
|     , view_toolbar(GLToolbar::Radio, "View") | ||||
| #else | ||||
|     , view_toolbar(GLToolbar::Radio) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     , m_project_filename(wxEmptyString) | ||||
| { | ||||
| 	this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); | ||||
|  | @ -3238,6 +3277,9 @@ void Plater::priv::set_project_filename(const wxString& filename) | |||
| 
 | ||||
|     m_project_filename = from_path(full_path); | ||||
|     wxGetApp().mainframe->update_title(); | ||||
| 
 | ||||
|     if (!filename.empty()) | ||||
|         wxGetApp().mainframe->add_to_recent_projects(filename); | ||||
| } | ||||
| 
 | ||||
| bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/) | ||||
|  | @ -3352,12 +3394,6 @@ bool Plater::priv::complit_init_part_menu() | |||
| 
 | ||||
| void Plater::priv::init_view_toolbar() | ||||
| { | ||||
| #if !ENABLE_SVG_ICONS | ||||
|     ItemsIconsTexture::Metadata icons_data; | ||||
|     icons_data.filename = "view_toolbar.png"; | ||||
|     icons_data.icon_size = 64; | ||||
| #endif // !ENABLE_SVG_ICONS
 | ||||
| 
 | ||||
|     BackgroundTexture::Metadata background_data; | ||||
|     background_data.filename = "toolbar_background.png"; | ||||
|     background_data.left = 16; | ||||
|  | @ -3365,11 +3401,7 @@ void Plater::priv::init_view_toolbar() | |||
|     background_data.right = 16; | ||||
|     background_data.bottom = 16; | ||||
| 
 | ||||
| #if ENABLE_SVG_ICONS | ||||
|     if (!view_toolbar.init(background_data)) | ||||
| #else | ||||
|     if (!view_toolbar.init(icons_data, background_data)) | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|         return; | ||||
| 
 | ||||
|     view_toolbar.set_layout_orientation(GLToolbar::Layout::Bottom); | ||||
|  | @ -3379,9 +3411,7 @@ void Plater::priv::init_view_toolbar() | |||
|     GLToolbarItem::Data item; | ||||
| 
 | ||||
|     item.name = "3D"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "editor.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]"; | ||||
|     item.sprite_id = 0; | ||||
|     item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; | ||||
|  | @ -3390,9 +3420,7 @@ void Plater::priv::init_view_toolbar() | |||
|         return; | ||||
| 
 | ||||
|     item.name = "Preview"; | ||||
| #if ENABLE_SVG_ICONS | ||||
|     item.icon_filename = "preview.svg"; | ||||
| #endif // ENABLE_SVG_ICONS
 | ||||
|     item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]"; | ||||
|     item.sprite_id = 1; | ||||
|     item.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; | ||||
|  | @ -3565,15 +3593,19 @@ void Plater::load_project() | |||
| { | ||||
|     wxString input_file; | ||||
|     wxGetApp().load_project(this, input_file); | ||||
|     load_project(input_file); | ||||
| } | ||||
| 
 | ||||
|     if (input_file.empty()) | ||||
| void Plater::load_project(const wxString& filename) | ||||
| { | ||||
|     if (filename.empty()) | ||||
|         return; | ||||
| 
 | ||||
|     p->reset(); | ||||
|     p->set_project_filename(input_file); | ||||
|     p->set_project_filename(filename); | ||||
| 
 | ||||
|     std::vector<fs::path> input_paths; | ||||
|     input_paths.push_back(into_path(input_file)); | ||||
|     input_paths.push_back(into_path(filename)); | ||||
|     load_files(input_paths); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -138,6 +138,7 @@ public: | |||
| 
 | ||||
|     void new_project(); | ||||
|     void load_project(); | ||||
|     void load_project(const wxString& filename); | ||||
|     void add_model(); | ||||
|     void extract_config_from_project(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -461,6 +461,7 @@ const std::vector<std::string>& Preset::sla_print_options() | |||
|             "support_pillar_widening_factor", | ||||
|             "support_base_diameter", | ||||
|             "support_base_height", | ||||
|             "support_base_safety_distance", | ||||
|             "support_critical_angle", | ||||
|             "support_max_bridge_length", | ||||
|             "support_max_pillar_link_distance", | ||||
|  | @ -474,6 +475,10 @@ const std::vector<std::string>& Preset::sla_print_options() | |||
|             "pad_max_merge_distance", | ||||
|             "pad_edge_radius", | ||||
|             "pad_wall_slope", | ||||
|             "pad_object_gap", | ||||
|             "pad_object_connector_stride", | ||||
|             "pad_object_connector_width", | ||||
|             "pad_object_connector_penetration", | ||||
|             "output_filename_format",  | ||||
|             "default_sla_print_profile", | ||||
|             "compatible_printers", | ||||
|  |  | |||
|  | @ -3515,6 +3515,7 @@ void TabSLAPrint::build() | |||
|     // optgroup->append_single_option_line("support_pillar_widening_factor");
 | ||||
|     optgroup->append_single_option_line("support_base_diameter"); | ||||
|     optgroup->append_single_option_line("support_base_height"); | ||||
|     optgroup->append_single_option_line("support_base_safety_distance"); | ||||
|     optgroup->append_single_option_line("support_object_elevation"); | ||||
| 
 | ||||
|     optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); | ||||
|  | @ -3536,6 +3537,11 @@ void TabSLAPrint::build() | |||
| //    optgroup->append_single_option_line("pad_edge_radius");
 | ||||
|     optgroup->append_single_option_line("pad_wall_slope"); | ||||
|      | ||||
|     optgroup->append_single_option_line("pad_object_gap"); | ||||
|     optgroup->append_single_option_line("pad_object_connector_stride"); | ||||
|     optgroup->append_single_option_line("pad_object_connector_width"); | ||||
|     optgroup->append_single_option_line("pad_object_connector_penetration"); | ||||
|      | ||||
| 	page = add_options_page(_(L("Advanced")), "wrench"); | ||||
| 	optgroup = page->new_optgroup(_(L("Slicing"))); | ||||
| 	optgroup->append_single_option_line("slice_closing_radius"); | ||||
|  | @ -3580,36 +3586,57 @@ void TabSLAPrint::update() | |||
| 
 | ||||
|      m_update_cnt++; | ||||
| 
 | ||||
|      double head_penetration = m_config->opt_float("support_head_penetration"); | ||||
|      double head_width = m_config->opt_float("support_head_width"); | ||||
|      if(head_penetration > head_width) { | ||||
|          wxString msg_text = _(L("Head penetration should not be greater than the head width.")); | ||||
|          auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); | ||||
|          DynamicPrintConfig new_conf = *m_config; | ||||
|          if (dialog->ShowModal() == wxID_OK) { | ||||
|              new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); | ||||
|          } | ||||
|     double head_penetration = m_config->opt_float("support_head_penetration"); | ||||
|     double head_width       = m_config->opt_float("support_head_width"); | ||||
|     if (head_penetration > head_width) { | ||||
|         wxString msg_text = _( | ||||
|             L("Head penetration should not be greater than the head width.")); | ||||
| 
 | ||||
|          load_config(new_conf); | ||||
|      } | ||||
|         auto dialog = new wxMessageDialog(parent(), | ||||
|                                           msg_text, | ||||
|                                           _(L("Invalid Head penetration")), | ||||
|                                           wxICON_WARNING | wxOK); | ||||
| 
 | ||||
|      double pinhead_d = m_config->opt_float("support_head_front_diameter"); | ||||
|      double pillar_d     = m_config->opt_float("support_pillar_diameter"); | ||||
|      if(pinhead_d > pillar_d) { | ||||
|          wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); | ||||
|          auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); | ||||
|          DynamicPrintConfig new_conf = *m_config; | ||||
|          if (dialog->ShowModal() == wxID_OK) { | ||||
|              new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); | ||||
|          } | ||||
|         DynamicPrintConfig new_conf = *m_config; | ||||
|         if (dialog->ShowModal() == wxID_OK) { | ||||
|             new_conf.set_key_value("support_head_penetration", | ||||
|                                    new ConfigOptionFloat(head_width)); | ||||
|         } | ||||
| 
 | ||||
|          load_config(new_conf); | ||||
|      } | ||||
|         load_config(new_conf); | ||||
|     } | ||||
| 
 | ||||
|      m_update_cnt--; | ||||
|     double pinhead_d = m_config->opt_float("support_head_front_diameter"); | ||||
|     double pillar_d  = m_config->opt_float("support_pillar_diameter"); | ||||
|     if (pinhead_d > pillar_d) { | ||||
|         wxString msg_text = _(L( | ||||
|             "Pinhead diameter should be smaller than the pillar diameter.")); | ||||
| 
 | ||||
|      if (m_update_cnt == 0) | ||||
|     wxGetApp().mainframe->on_config_changed(m_config); | ||||
|         auto dialog = new wxMessageDialog(parent(), | ||||
|                                           msg_text, | ||||
|                                           _(L("Invalid pinhead diameter")), | ||||
|                                           wxICON_WARNING | wxOK); | ||||
| 
 | ||||
|         DynamicPrintConfig new_conf = *m_config; | ||||
|         if (dialog->ShowModal() == wxID_OK) { | ||||
|             new_conf.set_key_value("support_head_front_diameter", | ||||
|                                    new ConfigOptionFloat(pillar_d / 2.0)); | ||||
|         } | ||||
| 
 | ||||
|         load_config(new_conf); | ||||
|     } | ||||
|      | ||||
|     // if(m_config->opt_float("support_object_elevation") < EPSILON &&
 | ||||
|     //    m_config->opt_bool("pad_enable")) {
 | ||||
|     //     // TODO: disable editding of:
 | ||||
|     //     // pad_object_connector_stride
 | ||||
|     //     // pad_object_connector_width
 | ||||
|     //     // pad_object_connector_penetration
 | ||||
|     // }
 | ||||
| 
 | ||||
|     m_update_cnt--; | ||||
| 
 | ||||
|     if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); | ||||
| } | ||||
| 
 | ||||
| } // GUI
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Enrico Turri
						Enrico Turri