mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 20:51:12 -06:00 
			
		
		
		
	Merge branch 'master' of https://github.com/prusa3d/Slic3r
This commit is contained in:
		
						commit
						2ba661cb76
					
				
					 33 changed files with 1890 additions and 1427 deletions
				
			
		|  | @ -14,6 +14,8 @@ public: | |||
|         localmethod_ = m; | ||||
|         return *this; | ||||
|     } | ||||
| 
 | ||||
|     inline void seed(unsigned long val) { nlopt::srand(val); } | ||||
| }; | ||||
| 
 | ||||
| template<> | ||||
|  |  | |||
|  | @ -487,7 +487,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ | |||
|     // starts analyzer calculations
 | ||||
|     if (m_enable_analyzer) { | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Preparing G-code preview data"; | ||||
|         m_analyzer.calc_gcode_preview_data(*preview_data); | ||||
|         m_analyzer.calc_gcode_preview_data(*preview_data, [print]() { print->throw_if_canceled(); }); | ||||
|         m_analyzer.reset(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -137,22 +137,22 @@ const std::string& GCodeAnalyzer::process_gcode(const std::string& gcode) | |||
|     return m_process_output; | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data) | ||||
| void GCodeAnalyzer::calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback) | ||||
| { | ||||
|     // resets preview data
 | ||||
|     preview_data.reset(); | ||||
| 
 | ||||
|     // calculates extrusion layers
 | ||||
|     _calc_gcode_preview_extrusion_layers(preview_data); | ||||
|     _calc_gcode_preview_extrusion_layers(preview_data, cancel_callback); | ||||
| 
 | ||||
|     // calculates travel
 | ||||
|     _calc_gcode_preview_travel(preview_data); | ||||
|     _calc_gcode_preview_travel(preview_data, cancel_callback); | ||||
| 
 | ||||
|     // calculates retractions
 | ||||
|     _calc_gcode_preview_retractions(preview_data); | ||||
|     _calc_gcode_preview_retractions(preview_data, cancel_callback); | ||||
| 
 | ||||
|     // calculates unretractions
 | ||||
|     _calc_gcode_preview_unretractions(preview_data); | ||||
|     _calc_gcode_preview_unretractions(preview_data, cancel_callback); | ||||
| } | ||||
| 
 | ||||
| bool GCodeAnalyzer::is_valid_extrusion_role(ExtrusionRole role) | ||||
|  | @ -676,7 +676,7 @@ bool GCodeAnalyzer::_is_valid_extrusion_role(int value) const | |||
|     return ((int)erNone <= value) && (value <= (int)erMixed); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data) | ||||
| void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback) | ||||
| { | ||||
|     struct Helper | ||||
|     { | ||||
|  | @ -725,9 +725,18 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ | |||
|     GCodePreviewData::Range feedrate_range; | ||||
|     GCodePreviewData::Range volumetric_rate_range; | ||||
| 
 | ||||
|     // to avoid to call the callback too often
 | ||||
|     unsigned int cancel_callback_threshold = (unsigned int)extrude_moves->second.size() / 25; | ||||
|     unsigned int cancel_callback_curr = 0; | ||||
| 
 | ||||
|     // constructs the polylines while traversing the moves
 | ||||
|     for (const GCodeMove& move : extrude_moves->second) | ||||
|     { | ||||
|         // to avoid to call the callback too often
 | ||||
|         cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; | ||||
|         if (cancel_callback_curr == 0) | ||||
|             cancel_callback(); | ||||
| 
 | ||||
|         if ((data != move.data) || (z != move.start_position.z()) || (position != move.start_position) || (volumetric_rate != move.data.feedrate * (float)move.data.mm3_per_mm)) | ||||
|         { | ||||
|             // store current polyline
 | ||||
|  | @ -769,7 +778,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ | |||
|     preview_data.ranges.volumetric_rate.update_from(volumetric_rate_range); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) | ||||
| void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback) | ||||
| { | ||||
|     struct Helper | ||||
|     { | ||||
|  | @ -797,9 +806,17 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) | |||
|     GCodePreviewData::Range width_range; | ||||
|     GCodePreviewData::Range feedrate_range; | ||||
| 
 | ||||
|     // to avoid to call the callback too often
 | ||||
|     unsigned int cancel_callback_threshold = (unsigned int)travel_moves->second.size() / 25; | ||||
|     unsigned int cancel_callback_curr = 0; | ||||
| 
 | ||||
|     // constructs the polylines while traversing the moves
 | ||||
|     for (const GCodeMove& move : travel_moves->second) | ||||
|     { | ||||
|         cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; | ||||
|         if (cancel_callback_curr == 0) | ||||
|             cancel_callback(); | ||||
| 
 | ||||
|         GCodePreviewData::Travel::EType move_type = (move.delta_extruder < 0.0f) ? GCodePreviewData::Travel::Retract : ((move.delta_extruder > 0.0f) ? GCodePreviewData::Travel::Extrude : GCodePreviewData::Travel::Move); | ||||
|         GCodePreviewData::Travel::Polyline::EDirection move_direction = ((move.start_position.x() != move.end_position.x()) || (move.start_position.y() != move.end_position.y())) ? GCodePreviewData::Travel::Polyline::Generic : GCodePreviewData::Travel::Polyline::Vertical; | ||||
| 
 | ||||
|  | @ -840,28 +857,44 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data) | |||
|     preview_data.ranges.feedrate.update_from(feedrate_range); | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data) | ||||
| void GCodeAnalyzer::_calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback) | ||||
| { | ||||
|     TypeToMovesMap::iterator retraction_moves = m_moves_map.find(GCodeMove::Retract); | ||||
|     if (retraction_moves == m_moves_map.end()) | ||||
|         return; | ||||
| 
 | ||||
|     // to avoid to call the callback too often
 | ||||
|     unsigned int cancel_callback_threshold = (unsigned int)retraction_moves->second.size() / 25; | ||||
|     unsigned int cancel_callback_curr = 0; | ||||
| 
 | ||||
|     for (const GCodeMove& move : retraction_moves->second) | ||||
|     { | ||||
|         cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; | ||||
|         if (cancel_callback_curr == 0) | ||||
|             cancel_callback(); | ||||
| 
 | ||||
|         // store position
 | ||||
|         Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); | ||||
|         preview_data.retraction.positions.emplace_back(position, move.data.width, move.data.height); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data) | ||||
| void GCodeAnalyzer::_calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback) | ||||
| { | ||||
|     TypeToMovesMap::iterator unretraction_moves = m_moves_map.find(GCodeMove::Unretract); | ||||
|     if (unretraction_moves == m_moves_map.end()) | ||||
|         return; | ||||
| 
 | ||||
|     // to avoid to call the callback too often
 | ||||
|     unsigned int cancel_callback_threshold = (unsigned int)unretraction_moves->second.size() / 25; | ||||
|     unsigned int cancel_callback_curr = 0; | ||||
| 
 | ||||
|     for (const GCodeMove& move : unretraction_moves->second) | ||||
|     { | ||||
|         cancel_callback_curr = (cancel_callback_curr + 1) % cancel_callback_threshold; | ||||
|         if (cancel_callback_curr == 0) | ||||
|             cancel_callback(); | ||||
| 
 | ||||
|         // store position
 | ||||
|         Vec3crd position(scale_(move.start_position.x()), scale_(move.start_position.y()), scale_(move.start_position.z())); | ||||
|         preview_data.unretraction.positions.emplace_back(position, move.data.width, move.data.height); | ||||
|  |  | |||
|  | @ -122,7 +122,8 @@ public: | |||
|     const std::string& process_gcode(const std::string& gcode); | ||||
| 
 | ||||
|     // Calculates all data needed for gcode visualization
 | ||||
|     void calc_gcode_preview_data(GCodePreviewData& preview_data); | ||||
|     // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
 | ||||
|     void calc_gcode_preview_data(GCodePreviewData& preview_data, std::function<void()> cancel_callback = std::function<void()>()); | ||||
| 
 | ||||
|     // Return an estimate of the memory consumed by the time estimator.
 | ||||
|     size_t memory_used() const; | ||||
|  | @ -237,10 +238,11 @@ private: | |||
|     // Checks if the given int is a valid extrusion role (contained into enum ExtrusionRole)
 | ||||
|     bool _is_valid_extrusion_role(int value) const; | ||||
| 
 | ||||
|     void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data); | ||||
|     void _calc_gcode_preview_travel(GCodePreviewData& preview_data); | ||||
|     void _calc_gcode_preview_retractions(GCodePreviewData& preview_data); | ||||
|     void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data); | ||||
|     // All the following methods throw CanceledException through print->throw_if_canceled() (sent by the caller as callback).
 | ||||
|     void _calc_gcode_preview_extrusion_layers(GCodePreviewData& preview_data, std::function<void()> cancel_callback); | ||||
|     void _calc_gcode_preview_travel(GCodePreviewData& preview_data, std::function<void()> cancel_callback); | ||||
|     void _calc_gcode_preview_retractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback); | ||||
|     void _calc_gcode_preview_unretractions(GCodePreviewData& preview_data, std::function<void()> cancel_callback); | ||||
| }; | ||||
| 
 | ||||
| } // namespace Slic3r
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| 
 | ||||
| #include <boost/algorithm/string.hpp> | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/format.hpp> | ||||
| #include <boost/filesystem.hpp> | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
|  | @ -88,7 +89,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line) | |||
| // Run the script. If it is a perl script, run it through the bundled perl interpreter.
 | ||||
| // If it is a batch file, run it through the cmd.exe.
 | ||||
| // Otherwise run it directly.
 | ||||
| static int run_script_win32(const std::string &script, const std::string &gcode) | ||||
| static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/) | ||||
| { | ||||
|     // Unpack the argument list provided by the user.
 | ||||
|     int     nArgs; | ||||
|  | @ -132,9 +133,46 @@ static int run_script_win32(const std::string &script, const std::string &gcode) | |||
| } | ||||
| 
 | ||||
| #else | ||||
|     #include <sys/stat.h> //for getting filesystem UID/GID
 | ||||
|     #include <unistd.h> //for getting current UID/GID
 | ||||
|     #include <boost/process.hpp> | ||||
|     // POSIX
 | ||||
| 
 | ||||
| #include <cstdlib>   // getenv()
 | ||||
| #include <sstream> | ||||
| #include <boost/process.hpp> | ||||
| 
 | ||||
| namespace process = boost::process; | ||||
| 
 | ||||
| static int run_script(const std::string &script, const std::string &gcode, std::string &std_err) | ||||
| { | ||||
|     // Try to obtain user's default shell
 | ||||
|     const char *shell = ::getenv("SHELL"); | ||||
|     if (shell == nullptr) { shell = "sh"; } | ||||
| 
 | ||||
|     // Quote and escape the gcode path argument
 | ||||
|     std::string command { script }; | ||||
|     command.append(" '"); | ||||
|     for (char c : gcode) { | ||||
|         if (c == '\'') { command.append("'\\''"); } | ||||
|         else { command.push_back(c); } | ||||
|     } | ||||
|     command.push_back('\''); | ||||
| 
 | ||||
|     BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command; | ||||
| 
 | ||||
|     process::ipstream istd_err; | ||||
|     process::child child(shell, "-c", command, process::std_err > istd_err); | ||||
| 
 | ||||
|     std_err.clear(); | ||||
|     std::string line; | ||||
| 
 | ||||
|     while (child.running() && std::getline(istd_err, line)) { | ||||
|         std_err.append(line); | ||||
|         std_err.push_back('\n'); | ||||
|     } | ||||
| 
 | ||||
|     child.wait(); | ||||
|     return child.exit_code(); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| namespace Slic3r { | ||||
|  | @ -158,27 +196,15 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config | |||
|             if (script.empty()) | ||||
|                 continue; | ||||
|             BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path; | ||||
| #ifdef WIN32 | ||||
|             int result = run_script_win32(script, gcode_file.string()); | ||||
| #else | ||||
|             //FIXME testing existence of a script is risky, as the script line may contain the script and some additional command line parameters.
 | ||||
|             // We would have to process the script line into parameters before testing for the existence of the command, the command may be looked up
 | ||||
|             // in the PATH etc.
 | ||||
|             if (! boost::filesystem::exists(boost::filesystem::path(script))) | ||||
|                 throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + script); | ||||
|             struct stat info; | ||||
|             if (stat(script.c_str(), &info)) | ||||
|                 throw std::runtime_error(std::string("Cannot read information for post-processing script: ") + script); | ||||
|             boost::filesystem::perms script_perms = boost::filesystem::status(script).permissions(); | ||||
|             //if UID matches, check UID perm. else if GID matches, check GID perm. Otherwise check other perm.
 | ||||
|             if (!(script_perms & ((info.st_uid == geteuid()) ? boost::filesystem::perms::owner_exe | ||||
|                                : ((info.st_gid == getegid()) ? boost::filesystem::perms::group_exe | ||||
|                                                              : boost::filesystem::perms::others_exe)))) | ||||
|                 throw std::runtime_error(std::string("The configured post-processing script is not executable: check permissions. ") + script); | ||||
|     		int result = boost::process::system(script, gcode_file); | ||||
|     		if (result < 0) | ||||
|     			BOOST_LOG_TRIVIAL(error) << "Script " << script << " on file " << path << " failed. Negative error code returned."; | ||||
| #endif | ||||
| 
 | ||||
|             std::string std_err; | ||||
|             const int result = run_script(script, gcode_file.string(), std_err); | ||||
|             if (result != 0) { | ||||
|                 const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str() | ||||
|                     : (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str(); | ||||
|                 BOOST_LOG_TRIVIAL(error) << msg; | ||||
|                 throw std::runtime_error(msg); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -709,12 +709,11 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::toolchange_Brim(bool sideOnly, flo | |||
|     // The tool is supposed to be active and primed at the time when the wipe tower brim is extruded.
 | ||||
|     // Extrude 4 rounds of a brim around the future wipe tower.
 | ||||
|     box_coordinates box(wipeTower_box); | ||||
|     box.expand(m_perimeter_width); | ||||
|     for (size_t i = 0; i < 4; ++ i) { | ||||
|         box.expand(m_perimeter_width - m_layer_height*(1.f-M_PI_4)); // the brim shall have 'normal' spacing with no extra void space
 | ||||
|         writer.travel (box.ld, 7000) | ||||
|                 .extrude(box.lu, 2100).extrude(box.ru) | ||||
|                 .extrude(box.rd      ).extrude(box.ld); | ||||
|         box.expand(m_perimeter_width); | ||||
|     } | ||||
| 
 | ||||
|     writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner.
 | ||||
|  |  | |||
|  | @ -1461,6 +1461,15 @@ int ModelVolume::extruder_id() const | |||
|     return extruder_id; | ||||
| } | ||||
| 
 | ||||
| bool ModelVolume::is_splittable() const | ||||
| { | ||||
|     // the call mesh.has_multiple_patches() is expensive, so cache the value to calculate it only once
 | ||||
|     if (m_is_splittable == -1) | ||||
|         m_is_splittable = (int)mesh.has_multiple_patches(); | ||||
| 
 | ||||
|     return m_is_splittable == 1; | ||||
| } | ||||
| 
 | ||||
| void ModelVolume::center_geometry() | ||||
| { | ||||
| #if ENABLE_VOLUMES_CENTERING_FIXES | ||||
|  |  | |||
|  | @ -340,8 +340,7 @@ public: | |||
|     // Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes).
 | ||||
|     int                 extruder_id() const; | ||||
| 
 | ||||
|     void                set_splittable(const int val) { m_is_splittable = val; } | ||||
|     int                 is_splittable() const { return m_is_splittable; } | ||||
|     bool                is_splittable() const; | ||||
| 
 | ||||
|     // Split this volume, append the result to the object owning this volume.
 | ||||
|     // Return the number of volumes created from this one.
 | ||||
|  | @ -421,7 +420,7 @@ private: | |||
|     //     -1   ->   is unknown value (before first cheking)
 | ||||
|     //      0   ->   is not splittable
 | ||||
|     //      1   ->   is splittable
 | ||||
|     int                     m_is_splittable {-1}; | ||||
|     mutable int               m_is_splittable{ -1 }; | ||||
| 
 | ||||
| 	ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object) | ||||
|     { | ||||
|  |  | |||
|  | @ -2450,6 +2450,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->enum_values.push_back("portrait"); | ||||
|     def->enum_labels.push_back(L("Landscape")); | ||||
|     def->enum_labels.push_back(L("Portrait")); | ||||
|     def->mode = comExpert; | ||||
|     def->default_value = new ConfigOptionEnum<SLADisplayOrientation>(sladoPortrait); | ||||
| 
 | ||||
|     def = this->add("fast_tilt_time", coFloat); | ||||
|  | @ -2563,6 +2564,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("Generate supports for the models"); | ||||
|     def->cli = ""; | ||||
|     def->mode = comSimple; | ||||
|     def->default_value = new ConfigOptionBool(true); | ||||
| 
 | ||||
|     def = this->add("support_head_front_diameter", coFloat); | ||||
|  | @ -2572,6 +2574,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(0.4); | ||||
| 
 | ||||
|     def = this->add("support_head_penetration", coFloat); | ||||
|  | @ -2580,6 +2583,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->tooltip = L("How much the pinhead has to penetrate the model surface"); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->mode = comAdvanced; | ||||
|     def->min = 0; | ||||
|     def->default_value = new ConfigOptionFloat(0.2); | ||||
| 
 | ||||
|  | @ -2590,6 +2594,8 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 20; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(1.0); | ||||
| 
 | ||||
|     def = this->add("support_pillar_diameter", coFloat); | ||||
|  | @ -2599,6 +2605,8 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 15; | ||||
|     def->mode = comSimple; | ||||
|     def->default_value = new ConfigOptionFloat(1.0); | ||||
| 
 | ||||
|     def = this->add("support_pillar_connection_mode", coEnum); | ||||
|  | @ -2615,6 +2623,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->enum_labels.push_back(L("Zig-Zag")); | ||||
|     def->enum_labels.push_back(L("Cross")); | ||||
|     def->enum_labels.push_back(L("Dynamic")); | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionEnum<SLAPillarConnectionMode>(slapcmDynamic); | ||||
| 
 | ||||
|     def = this->add("support_buildplate_only", coBool); | ||||
|  | @ -2634,6 +2643,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 1; | ||||
|     def->mode = comExpert; | ||||
|     def->default_value = new ConfigOptionFloat(0.0); | ||||
| 
 | ||||
|     def = this->add("support_base_diameter", coFloat); | ||||
|  | @ -2643,6 +2653,8 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 30; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(4.0); | ||||
| 
 | ||||
|     def = this->add("support_base_height", coFloat); | ||||
|  | @ -2652,6 +2664,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(1.0); | ||||
| 
 | ||||
|     def = this->add("support_critical_angle", coFloat); | ||||
|  | @ -2661,6 +2674,8 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("°"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 90; | ||||
|     def->mode = comExpert; | ||||
|     def->default_value = new ConfigOptionFloat(45); | ||||
| 
 | ||||
|     def = this->add("support_max_bridge_length", coFloat); | ||||
|  | @ -2670,8 +2685,20 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(15.0); | ||||
| 
 | ||||
|     def = this->add("support_max_pillar_link_distance", coFloat); | ||||
|     def->label = L("Max pillar linking distance"); | ||||
|     def->category = L("Supports"); | ||||
|     def->tooltip = L("The max distance of two pillars to get linked with each other." | ||||
|                      " A zero value will prohibit pillar cascading."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0;   // 0 means no linking
 | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(10.0); | ||||
| 
 | ||||
|     def = this->add("support_object_elevation", coFloat); | ||||
|     def->label = L("Object elevation"); | ||||
|     def->category = L("Supports"); | ||||
|  | @ -2679,6 +2706,8 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 150; // This is the max height of print on SL1
 | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(5.0); | ||||
| 
 | ||||
|     def = this->add("support_points_density_relative", coInt); | ||||
|  | @ -2704,35 +2733,46 @@ void PrintConfigDef::init_sla_params() | |||
|     def->category = L("Pad"); | ||||
|     def->tooltip = L("Add a pad underneath the supported model"); | ||||
|     def->cli = ""; | ||||
|     def->mode = comSimple; | ||||
|     def->default_value = new ConfigOptionBool(true); | ||||
| 
 | ||||
|     def = this->add("pad_wall_thickness", coFloat); | ||||
|     def->label = L("Pad wall thickness"); | ||||
|     def->category = L("Pad"); | ||||
| //     def->tooltip = L("");
 | ||||
|      def->tooltip = L("The thickness of the pad and its optional cavity walls."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 30; | ||||
|     def->mode = comSimple; | ||||
|     def->default_value = new ConfigOptionFloat(2.0); | ||||
| 
 | ||||
|     def = this->add("pad_wall_height", coFloat); | ||||
|     def->label = L("Pad wall height"); | ||||
|     def->tooltip = L("Defines the cavity depth. Set to zero to disable the cavity."); | ||||
|     def->category = L("Pad"); | ||||
| //     def->tooltip = L("");
 | ||||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->max = 30; | ||||
|     def->mode = comSimple; | ||||
|     def->default_value = new ConfigOptionFloat(5.0); | ||||
| 
 | ||||
|     def = this->add("pad_max_merge_distance", coFloat); | ||||
|     def->label = L("Max merge distance"); | ||||
|     def->category = L("Pad"); | ||||
| //     def->tooltip = L("");
 | ||||
|      def->tooltip = L("Some objects can get along with a few smaller pads " | ||||
|                       "instead of a single big one. This parameter defines " | ||||
|                       "how far the center of two smaller pads should be. If they" | ||||
|                       "are closer, they will get merged into one pad."); | ||||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->mode = comExpert; | ||||
|     def->default_value = new ConfigOptionFloat(50.0); | ||||
| 
 | ||||
|     // This is disabled on the UI. I hope it will never be enabled.
 | ||||
|     def = this->add("pad_edge_radius", coFloat); | ||||
|     def->label = L("Pad edge radius"); | ||||
|     def->category = L("Pad"); | ||||
|  | @ -2740,6 +2780,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->sidetext = L("mm"); | ||||
|     def->cli = ""; | ||||
|     def->min = 0; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(1.0); | ||||
| 
 | ||||
|     def = this->add("pad_wall_slope", coFloat); | ||||
|  | @ -2751,6 +2792,7 @@ void PrintConfigDef::init_sla_params() | |||
|     def->cli = ""; | ||||
|     def->min = 45; | ||||
|     def->max = 90; | ||||
|     def->mode = comAdvanced; | ||||
|     def->default_value = new ConfigOptionFloat(45.0); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1008,6 +1008,9 @@ public: | |||
|     // The max length of a bridge in mm
 | ||||
|     ConfigOptionFloat support_max_bridge_length /*= 15.0*/; | ||||
| 
 | ||||
|     // The max distance of two pillars to get cross linked.
 | ||||
|     ConfigOptionFloat support_max_pillar_link_distance; | ||||
| 
 | ||||
|     // The elevation in Z direction upwards. This is the space between the pad
 | ||||
|     // and the model object's bounding box bottom. Units in mm.
 | ||||
|     ConfigOptionFloat support_object_elevation /*= 5.0*/; | ||||
|  | @ -1055,6 +1058,7 @@ protected: | |||
|         OPT_PTR(support_base_height); | ||||
|         OPT_PTR(support_critical_angle); | ||||
|         OPT_PTR(support_max_bridge_length); | ||||
|         OPT_PTR(support_max_pillar_link_distance); | ||||
|         OPT_PTR(support_points_density_relative); | ||||
|         OPT_PTR(support_points_minimal_distance); | ||||
|         OPT_PTR(support_object_elevation); | ||||
|  |  | |||
|  | @ -496,7 +496,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru | |||
|         poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); | ||||
|     } | ||||
|     for (const Vec2f &pt : poisson_samples) { | ||||
|         m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island); | ||||
|         m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, m_config.head_diameter/2.f, is_new_island); | ||||
|         structure.supports_force_this_layer += m_config.support_force(); | ||||
|         grid3d.insert(pt, &structure); | ||||
|     } | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ public: | |||
|     struct Config { | ||||
|             float density_relative; | ||||
|             float minimal_distance; | ||||
|             float head_diameter; | ||||
|             ///////////////
 | ||||
|             inline float support_force() const { return 10.f / density_relative; } // a force one point can support       (arbitrary force unit)
 | ||||
|             inline float tear_pressure() const { return 1.f; }  // pressure that the display exerts    (the force unit per mm2)
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| #define SLACOMMON_HPP | ||||
| 
 | ||||
| #include <Eigen/Geometry> | ||||
| #include <memory> | ||||
| 
 | ||||
| // #define SLIC3R_SLA_NEEDS_WINDTREE
 | ||||
| 
 | ||||
|  | @ -44,7 +45,6 @@ struct SupportPoint { | |||
|     bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /// An index-triangle structure for libIGL functions. Also serves as an
 | ||||
| /// alternative (raw) input format for the SLASupportTree
 | ||||
| /*struct EigenMesh3D {
 | ||||
|  | @ -78,24 +78,38 @@ public: | |||
| 
 | ||||
|     // Result of a raycast
 | ||||
|     class hit_result { | ||||
|         double m_t = std::numeric_limits<double>::infinity(); | ||||
|         double m_t = std::nan(""); | ||||
|         int m_face_id = -1; | ||||
|         const EigenMesh3D& m_mesh; | ||||
|         const EigenMesh3D *m_mesh = nullptr; | ||||
|         Vec3d m_dir; | ||||
|         inline hit_result(const EigenMesh3D& em): m_mesh(em) {} | ||||
|         Vec3d m_source; | ||||
|         friend class EigenMesh3D; | ||||
| 
 | ||||
|         // A valid object of this class can only be obtained from
 | ||||
|         // EigenMesh3D::query_ray_hit method.
 | ||||
|         explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {} | ||||
|     public: | ||||
| 
 | ||||
|         // This can create a placeholder object which is invalid (not created
 | ||||
|         // by a query_ray_hit call) but the distance can be preset to
 | ||||
|         // a specific value for distinguishing the placeholder.
 | ||||
|         inline hit_result(double val = std::nan("")): m_t(val) {} | ||||
| 
 | ||||
|         inline double distance() const { return m_t; } | ||||
|         inline const Vec3d& direction() const { return m_dir; } | ||||
|         inline Vec3d position() const { return m_source + m_dir * m_t; } | ||||
|         inline int face() const { return m_face_id; } | ||||
|         inline bool is_valid() const { return m_mesh != nullptr; } | ||||
| 
 | ||||
|         // Hit_result can decay into a double as the hit distance.
 | ||||
|         inline operator double() const { return distance(); } | ||||
| 
 | ||||
|         inline Vec3d normal() const { | ||||
|             if(m_face_id < 0) return {}; | ||||
|             auto trindex    = m_mesh.m_F.row(m_face_id); | ||||
|             const Vec3d& p1 = m_mesh.V().row(trindex(0)); | ||||
|             const Vec3d& p2 = m_mesh.V().row(trindex(1)); | ||||
|             const Vec3d& p3 = m_mesh.V().row(trindex(2)); | ||||
|             if(m_face_id < 0 || !is_valid()) return {}; | ||||
|             auto trindex    = m_mesh->m_F.row(m_face_id); | ||||
|             const Vec3d& p1 = m_mesh->V().row(trindex(0)); | ||||
|             const Vec3d& p2 = m_mesh->V().row(trindex(1)); | ||||
|             const Vec3d& p3 = m_mesh->V().row(trindex(2)); | ||||
|             Eigen::Vector3d U = p2 - p1; | ||||
|             Eigen::Vector3d V = p3 - p1; | ||||
|             return U.cross(V).normalized(); | ||||
|  | @ -133,6 +147,8 @@ public: | |||
| 
 | ||||
|     bool inside(const Vec3d& p) const; | ||||
| #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ | ||||
| 
 | ||||
|     double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -142,4 +158,4 @@ public: | |||
| } // namespace Slic3r
 | ||||
| 
 | ||||
| 
 | ||||
| #endif // SLASUPPORTTREE_HPP
 | ||||
| #endif // SLASUPPORTTREE_HPP
 | ||||
|  |  | |||
|  | @ -43,6 +43,8 @@ public: | |||
|     // For testing
 | ||||
|     size_t size() const; | ||||
|     bool empty() const { return size() == 0; } | ||||
| 
 | ||||
|     void foreach(std::function<void(const SpatElement& el)> fn); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -50,11 +50,6 @@ struct SupportConfig { | |||
|     // Width in mm from the back sphere center to the front sphere center.
 | ||||
|     double head_width_mm = 1.0; | ||||
| 
 | ||||
|     // Radius in mm of the support pillars. The actual radius of the pillars
 | ||||
|     // beginning with a head will not be higher than head_back_radius but the
 | ||||
|     // headless pillars will have half of this value.
 | ||||
|     double headless_pillar_radius_mm = 0.4; | ||||
| 
 | ||||
|     // How to connect pillars
 | ||||
|     PillarConnectionMode pillar_connection_mode = PillarConnectionMode::dynamic; | ||||
| 
 | ||||
|  | @ -74,18 +69,34 @@ struct SupportConfig { | |||
|     double base_height_mm = 1.0; | ||||
| 
 | ||||
|     // The default angle for connecting support sticks and junctions.
 | ||||
|     double tilt = M_PI/4; | ||||
|     double bridge_slope = M_PI/4; | ||||
| 
 | ||||
|     // The max length of a bridge in mm
 | ||||
|     double max_bridge_length_mm = 15.0; | ||||
|     double max_bridge_length_mm = 10.0; | ||||
| 
 | ||||
|     // The max distance of a pillar to pillar link.
 | ||||
|     double max_pillar_link_distance_mm = 10.0; | ||||
| 
 | ||||
|     // The elevation in Z direction upwards. This is the space between the pad
 | ||||
|     // and the model object's bounding box bottom.
 | ||||
|     double object_elevation_mm = 10; | ||||
| 
 | ||||
|     // The max Z angle for a normal at which it will get completely ignored.
 | ||||
|     double normal_cutoff_angle = 150.0 * M_PI / 180.0; | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
|     // Compile time configuration values (candidates for runtime)
 | ||||
|     // /////////////////////////////////////////////////////////////////////////
 | ||||
| 
 | ||||
|     // The max Z angle for a normal at which it will get completely ignored.
 | ||||
|     static const double normal_cutoff_angle; | ||||
| 
 | ||||
|     // The shortest distance of any support structure from the model surface
 | ||||
|     static const double safety_distance_mm; | ||||
| 
 | ||||
|     static const double max_solo_pillar_height_mm; | ||||
|     static const double max_dual_pillar_height_mm; | ||||
|     static const double   optimizer_rel_score_diff; | ||||
|     static const unsigned optimizer_max_iterations; | ||||
|     static const unsigned pillar_cascade_neighbors; | ||||
|     static const unsigned max_bridges_on_pillar; | ||||
| }; | ||||
| 
 | ||||
| struct PoolConfig; | ||||
|  | @ -116,21 +127,14 @@ using PointSet = Eigen::MatrixXd; | |||
| //EigenMesh3D to_eigenmesh(const ModelObject& model);
 | ||||
| 
 | ||||
| // Simple conversion of 'vector of points' to an Eigen matrix
 | ||||
| PointSet    to_point_set(const std::vector<sla::SupportPoint>&); | ||||
| //PointSet    to_point_set(const std::vector<sla::SupportPoint>&);
 | ||||
| 
 | ||||
| 
 | ||||
| /* ************************************************************************** */ | ||||
| 
 | ||||
| /// Just a wrapper to the runtime error to be recognizable in try blocks
 | ||||
| class SLASupportsStoppedException: public std::runtime_error { | ||||
| public: | ||||
|     using std::runtime_error::runtime_error; | ||||
|     SLASupportsStoppedException(); | ||||
| }; | ||||
| 
 | ||||
| /// The class containing mesh data for the generated supports.
 | ||||
| class SLASupportTree { | ||||
|     class Impl; | ||||
|     class Impl;     // persistent support data
 | ||||
|     std::unique_ptr<Impl> m_impl; | ||||
| 
 | ||||
|     Impl& get() { return *m_impl; } | ||||
|  | @ -140,16 +144,25 @@ class SLASupportTree { | |||
|                                  const SupportConfig&, | ||||
|                                  const Controller&); | ||||
| 
 | ||||
|     /// Generate the 3D supports for a model intended for SLA print.
 | ||||
|     bool generate(const PointSet& pts, | ||||
|     // The generation algorithm is quite long and will be captured in a separate
 | ||||
|     // class with private data, helper methods, etc... This data is only needed
 | ||||
|     // during the calculation whereas the Impl class contains the persistent
 | ||||
|     // data, mostly the meshes.
 | ||||
|     class Algorithm; | ||||
| 
 | ||||
|     // Generate the 3D supports for a model intended for SLA print. This
 | ||||
|     // will instantiate the Algorithm class and call its appropriate methods
 | ||||
|     // with status indication.
 | ||||
|     bool generate(const std::vector<SupportPoint>& pts, | ||||
|                   const EigenMesh3D& mesh, | ||||
|                   const SupportConfig& cfg = {}, | ||||
|                   const Controller& ctl = {}); | ||||
| 
 | ||||
| public: | ||||
| 
 | ||||
|     SLASupportTree(); | ||||
| 
 | ||||
|     SLASupportTree(const PointSet& pts, | ||||
|     SLASupportTree(const std::vector<SupportPoint>& pts, | ||||
|                    const EigenMesh3D& em, | ||||
|                    const SupportConfig& cfg = {}, | ||||
|                    const Controller& ctl = {}); | ||||
|  |  | |||
|  | @ -17,6 +17,8 @@ | |||
| #include <igl/remove_duplicate_vertices.h> | ||||
| #include <igl/signed_distance.h> | ||||
| 
 | ||||
| #include <tbb/parallel_for.h> | ||||
| 
 | ||||
| #include "SLASpatIndex.hpp" | ||||
| #include "ClipperUtils.hpp" | ||||
| 
 | ||||
|  | @ -89,6 +91,11 @@ size_t SpatIndex::size() const | |||
|     return m_impl->m_store.size(); | ||||
| } | ||||
| 
 | ||||
| void SpatIndex::foreach(std::function<void (const SpatElement &)> fn) | ||||
| { | ||||
|     for(auto& el : m_impl->m_store) fn(el); | ||||
| } | ||||
| 
 | ||||
| /* ****************************************************************************
 | ||||
|  * EigenMesh3D implementation | ||||
|  * ****************************************************************************/ | ||||
|  | @ -167,6 +174,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const | |||
|     hit_result ret(*this); | ||||
|     ret.m_t = double(hit.t); | ||||
|     ret.m_dir = dir; | ||||
|     ret.m_source = s; | ||||
|     if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id; | ||||
| 
 | ||||
|     return ret; | ||||
|  | @ -186,6 +194,15 @@ bool EigenMesh3D::inside(const Vec3d &p) const { | |||
| } | ||||
| #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ | ||||
| 
 | ||||
| double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { | ||||
|     double sqdst = 0; | ||||
|     Eigen::Matrix<double, 1, 3> pp = p; | ||||
|     Eigen::Matrix<double, 1, 3> cc; | ||||
|     sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc); | ||||
|     c = cc; | ||||
|     return sqdst; | ||||
| } | ||||
| 
 | ||||
| /* ****************************************************************************
 | ||||
|  * Misc functions | ||||
|  * ****************************************************************************/ | ||||
|  | @ -208,22 +225,31 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) { | |||
| PointSet normals(const PointSet& points, | ||||
|                  const EigenMesh3D& mesh, | ||||
|                  double eps, | ||||
|                  std::function<void()> throw_on_cancel) | ||||
|                  std::function<void()> thr, // throw on cancel
 | ||||
|                  const std::vector<unsigned>& pt_indices = {}) | ||||
| { | ||||
|     if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) | ||||
|         return {}; | ||||
| 
 | ||||
|     Eigen::VectorXd dists; | ||||
|     Eigen::VectorXi I; | ||||
|     PointSet C; | ||||
|     std::vector<unsigned> range = pt_indices; | ||||
|     if(range.empty()) { | ||||
|         range.resize(size_t(points.rows()), 0); | ||||
|         std::iota(range.begin(), range.end(), 0); | ||||
|     } | ||||
| 
 | ||||
|     igl::point_mesh_squared_distance( points, mesh.V(), mesh.F(), dists, I, C); | ||||
|     PointSet            ret(range.size(), 3); | ||||
| 
 | ||||
|     PointSet ret(I.rows(), 3); | ||||
|     for(int i = 0; i < I.rows(); i++) { | ||||
|         throw_on_cancel(); | ||||
|         auto idx = I(i); | ||||
|         auto trindex = mesh.F().row(idx); | ||||
|     tbb::parallel_for(size_t(0), range.size(), | ||||
|                       [&ret, &range, &mesh, &points, thr, eps](size_t ridx) | ||||
|     { | ||||
|         thr(); | ||||
|         auto eidx = Eigen::Index(range[ridx]); | ||||
|         int faceid = 0; | ||||
|         Vec3d p; | ||||
| 
 | ||||
|         mesh.squared_distance(points.row(eidx), faceid, p); | ||||
| 
 | ||||
|         auto trindex = mesh.F().row(faceid); | ||||
| 
 | ||||
|         const Vec3d& p1 = mesh.V().row(trindex(0)); | ||||
|         const Vec3d& p2 = mesh.V().row(trindex(1)); | ||||
|  | @ -237,8 +263,6 @@ PointSet normals(const PointSet& points, | |||
|         // of its triangle. The procedure is the same, get the neighbor
 | ||||
|         // triangles and calculate an average normal.
 | ||||
| 
 | ||||
|         const Vec3d& p = C.row(i); | ||||
| 
 | ||||
|         // mark the vertex indices of the edge. ia and ib marks and edge ic
 | ||||
|         // will mark a single vertex.
 | ||||
|         int ia = -1, ib = -1, ic = -1; | ||||
|  | @ -266,7 +290,7 @@ PointSet normals(const PointSet& points, | |||
|         std::vector<Vec3i> neigh; | ||||
|         if(ic >= 0) { // The point is right on a vertex of the triangle
 | ||||
|             for(int n = 0; n < mesh.F().rows(); ++n) { | ||||
|                 throw_on_cancel(); | ||||
|                 thr(); | ||||
|                 Vec3i ni = mesh.F().row(n); | ||||
|                 if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) | ||||
|                     neigh.emplace_back(ni); | ||||
|  | @ -275,7 +299,7 @@ PointSet normals(const PointSet& points, | |||
|         else if(ia >= 0 && ib >= 0) { // the point is on and edge
 | ||||
|             // now get all the neigboring triangles
 | ||||
|             for(int n = 0; n < mesh.F().rows(); ++n) { | ||||
|                 throw_on_cancel(); | ||||
|                 thr(); | ||||
|                 Vec3i ni = mesh.F().row(n); | ||||
|                 if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && | ||||
|                    (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) | ||||
|  | @ -316,52 +340,32 @@ PointSet normals(const PointSet& points, | |||
|             Vec3d sumnorm(0, 0, 0); | ||||
|             sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm); | ||||
|             sumnorm.normalize(); | ||||
|             ret.row(i) = sumnorm; | ||||
|             ret.row(long(ridx)) = sumnorm; | ||||
|         } | ||||
|         else { // point lies safely within its triangle
 | ||||
|             Eigen::Vector3d U = p2 - p1; | ||||
|             Eigen::Vector3d V = p3 - p1; | ||||
|             ret.row(i) = U.cross(V).normalized(); | ||||
|             ret.row(long(ridx)) = U.cross(V).normalized(); | ||||
|         } | ||||
|     } | ||||
|     }); | ||||
| 
 | ||||
|     return ret; | ||||
| } | ||||
| namespace bgi = boost::geometry::index; | ||||
| using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|         const sla::PointSet& points, | ||||
|         std::function<bool(const SpatElement&, const SpatElement&)> pred, | ||||
|         unsigned max_points = 0) | ||||
| ClusteredPoints cluster(Index3D& sindex, unsigned max_points, | ||||
|                         std::function<std::vector<SpatElement>(const Index3D&, const SpatElement&)> qfn) | ||||
| { | ||||
| 
 | ||||
|     namespace bgi = boost::geometry::index; | ||||
|     using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; | ||||
| 
 | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
| 
 | ||||
|     // Build the index
 | ||||
|     for(unsigned idx = 0; idx < points.rows(); idx++) | ||||
|         sindex.insert( std::make_pair(points.row(idx), idx)); | ||||
| 
 | ||||
|     using Elems = std::vector<SpatElement>; | ||||
| 
 | ||||
|     // Recursive function for visiting all the points in a given distance to
 | ||||
|     // each other
 | ||||
|     std::function<void(Elems&, Elems&)> group = | ||||
|     [&sindex, &group, pred, max_points](Elems& pts, Elems& cluster) | ||||
|     [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) | ||||
|     { | ||||
|         for(auto& p : pts) { | ||||
|             std::vector<SpatElement> tmp; | ||||
| 
 | ||||
|             sindex.query( | ||||
|                 bgi::satisfies([p, pred](const SpatElement& se) { | ||||
|                     return pred(p, se); | ||||
|                 }), | ||||
|                 std::back_inserter(tmp) | ||||
|             ); | ||||
| 
 | ||||
|             std::vector<SpatElement> tmp = qfn(sindex, p); | ||||
|             auto cmp = [](const SpatElement& e1, const SpatElement& e2){ | ||||
|                 return e1.second < e2.second; | ||||
|             }; | ||||
|  | @ -405,5 +409,84 @@ ClusteredPoints cluster( | |||
|     return result; | ||||
| } | ||||
| 
 | ||||
| namespace { | ||||
| std::vector<SpatElement> distance_queryfn(const Index3D& sindex, | ||||
|                                           const SpatElement& p, | ||||
|                                           double dist, | ||||
|                                           unsigned max_points) | ||||
| { | ||||
|     std::vector<SpatElement> tmp; tmp.reserve(max_points); | ||||
|     sindex.query( | ||||
|         bgi::nearest(p.first, max_points), | ||||
|         std::back_inserter(tmp) | ||||
|     ); | ||||
| 
 | ||||
|     for(auto it = tmp.begin(); it < tmp.end(); ++it) | ||||
|         if(distance(p.first, it->first) > dist) it = tmp.erase(it); | ||||
| 
 | ||||
|     return tmp; | ||||
| } | ||||
| } | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|         const std::vector<unsigned>& indices, | ||||
|         std::function<Vec3d(unsigned)> pointfn, | ||||
|         double dist, | ||||
|         unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
| 
 | ||||
|     // Build the index
 | ||||
|     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) | ||||
|     { | ||||
|         return distance_queryfn(sidx, p, dist, max_points); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| // Clustering a set of points by the given criteria
 | ||||
| ClusteredPoints cluster( | ||||
|         const std::vector<unsigned>& indices, | ||||
|         std::function<Vec3d(unsigned)> pointfn, | ||||
|         std::function<bool(const SpatElement&, const SpatElement&)> predicate, | ||||
|         unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
| 
 | ||||
|     // Build the index
 | ||||
|     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) | ||||
|     { | ||||
|         std::vector<SpatElement> tmp; tmp.reserve(max_points); | ||||
|         sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ | ||||
|             return predicate(p, e); | ||||
|         }), std::back_inserter(tmp)); | ||||
|         return tmp; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) | ||||
| { | ||||
|     // A spatial index for querying the nearest points
 | ||||
|     Index3D sindex; | ||||
| 
 | ||||
|     // Build the index
 | ||||
|     for(Eigen::Index i = 0; i < pts.rows(); i++) | ||||
|         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) | ||||
|     { | ||||
|         return distance_queryfn(sidx, p, dist, max_points); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -545,9 +545,9 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { | |||
|     scfg.head_penetration_mm = c.support_head_penetration.getFloat(); | ||||
|     scfg.head_width_mm = c.support_head_width.getFloat(); | ||||
|     scfg.object_elevation_mm = c.support_object_elevation.getFloat(); | ||||
|     scfg.tilt = c.support_critical_angle.getFloat() * PI / 180.0 ; | ||||
|     scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; | ||||
|     scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); | ||||
|     scfg.headless_pillar_radius_mm = 0.375*c.support_pillar_diameter.getFloat(); | ||||
|     scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); | ||||
|     switch(c.support_pillar_connection_mode.getInt()) { | ||||
|     case slapcmZigZag: | ||||
|         scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break; | ||||
|  | @ -674,6 +674,7 @@ void SLAPrint::process() | |||
|             // the density config value is in percents:
 | ||||
|             config.density_relative = float(cfg.support_points_density_relative / 100.f); | ||||
|             config.minimal_distance = float(cfg.support_points_minimal_distance); | ||||
|             config.head_diameter    = float(cfg.support_head_front_diameter); | ||||
| 
 | ||||
|             // Construction of this object does the calculation.
 | ||||
|             this->throw_if_canceled(); | ||||
|  | @ -711,54 +712,52 @@ void SLAPrint::process() | |||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             sla::SupportConfig scfg = make_support_cfg(po.m_config); | ||||
|             sla::Controller ctl; | ||||
|         sla::SupportConfig scfg = make_support_cfg(po.m_config); | ||||
|         sla::Controller ctl; | ||||
| 
 | ||||
|             // some magic to scale the status values coming from the support
 | ||||
|             // tree creation into the whole print process
 | ||||
|             auto stfirst = OBJ_STEP_LEVELS.begin(); | ||||
|             auto stthis = stfirst + slaposSupportTree; | ||||
|             // we need to add up the status portions until this operation
 | ||||
|             int init = std::accumulate(stfirst, stthis, 0); | ||||
|             init = int(init * ostepd);     // scale the init portion
 | ||||
|         // some magic to scale the status values coming from the support
 | ||||
|         // tree creation into the whole print process
 | ||||
|         auto stfirst = OBJ_STEP_LEVELS.begin(); | ||||
|         auto stthis = stfirst + slaposSupportTree; | ||||
|         // we need to add up the status portions until this operation
 | ||||
|         int init = std::accumulate(stfirst, stthis, 0); | ||||
|         init = int(init * ostepd);     // scale the init portion
 | ||||
| 
 | ||||
|             // scaling for the sub operations
 | ||||
|             double d = *stthis / (objcount * 100.0); | ||||
|         // scaling for the sub operations
 | ||||
|         double d = *stthis / (objcount * 100.0); | ||||
| 
 | ||||
|             ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) | ||||
|             { | ||||
|                 //FIXME this status line scaling does not seem to be correct.
 | ||||
|                 // How does it account for an increasing object index?
 | ||||
|                 report_status(*this, int(init + st*d), msg); | ||||
|             }; | ||||
|         ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) | ||||
|         { | ||||
|             //FIXME this status line scaling does not seem to be correct.
 | ||||
|             // How does it account for an increasing object index?
 | ||||
|             report_status(*this, int(init + st*d), msg); | ||||
|         }; | ||||
| 
 | ||||
|             ctl.stopcondition = [this](){ return canceled(); }; | ||||
|             ctl.cancelfn = [this]() { throw_if_canceled(); }; | ||||
|         ctl.stopcondition = [this](){ return canceled(); }; | ||||
|         ctl.cancelfn = [this]() { throw_if_canceled(); }; | ||||
| 
 | ||||
|             po.m_supportdata->support_tree_ptr.reset( | ||||
|                         new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points), | ||||
|                                            po.m_supportdata->emesh, scfg, ctl)); | ||||
|         po.m_supportdata->support_tree_ptr.reset( | ||||
|                     new SLASupportTree(po.m_supportdata->support_points, | ||||
|                                        po.m_supportdata->emesh, scfg, ctl)); | ||||
| 
 | ||||
|             // Create the unified mesh
 | ||||
|             auto rc = SlicingStatus::RELOAD_SCENE; | ||||
|         throw_if_canceled(); | ||||
| 
 | ||||
|             // This is to prevent "Done." being displayed during merged_mesh()
 | ||||
|             report_status(*this, -1, L("Visualizing supports")); | ||||
|             po.m_supportdata->support_tree_ptr->merged_mesh(); | ||||
|         // Create the unified mesh
 | ||||
|         auto rc = SlicingStatus::RELOAD_SCENE; | ||||
| 
 | ||||
|             BOOST_LOG_TRIVIAL(debug) << "Processed support point count " | ||||
|                                      << po.m_supportdata->support_points.size(); | ||||
|         // This is to prevent "Done." being displayed during merged_mesh()
 | ||||
|         report_status(*this, -1, L("Visualizing supports")); | ||||
|         po.m_supportdata->support_tree_ptr->merged_mesh(); | ||||
| 
 | ||||
|             // Check the mesh for later troubleshooting.
 | ||||
|             if(po.support_mesh().empty()) | ||||
|                 BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; | ||||
|         BOOST_LOG_TRIVIAL(debug) << "Processed support point count " | ||||
|                                  << po.m_supportdata->support_points.size(); | ||||
| 
 | ||||
|         // Check the mesh for later troubleshooting.
 | ||||
|         if(po.support_mesh().empty()) | ||||
|             BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; | ||||
| 
 | ||||
|         report_status(*this, -1, L("Visualizing supports"), rc); | ||||
| 
 | ||||
|             report_status(*this, -1, L("Visualizing supports"), rc); | ||||
|         } catch(sla::SLASupportsStoppedException&) { | ||||
|             // no need to rethrow
 | ||||
|             // throw_if_canceled();
 | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     // This step generates the sla base pad
 | ||||
|  | @ -1390,6 +1389,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf | |||
|             || opt_key == "support_base_height" | ||||
|             || opt_key == "support_critical_angle" | ||||
|             || opt_key == "support_max_bridge_length" | ||||
|             || opt_key == "support_max_pillar_link_distance" | ||||
|             || opt_key == "support_object_elevation") { | ||||
|             steps.emplace_back(slaposSupportTree); | ||||
|         } else if ( | ||||
|  |  | |||
|  | @ -247,6 +247,11 @@ private: | |||
|     // Invalidate steps based on a set of parameters changed.
 | ||||
|     bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys); | ||||
| 
 | ||||
|     std::vector<float> calculate_heights(const BoundingBoxf3& bb, | ||||
|                                          float elevation, | ||||
|                                          float initial_layer_height, | ||||
|                                          float layer_height) const; | ||||
| 
 | ||||
|     void fill_statistics(); | ||||
| 
 | ||||
|     SLAPrintConfig                  m_print_config; | ||||
|  | @ -270,8 +275,6 @@ private: | |||
|             lref(std::cref(lyr)), copies(std::cref(cp)) {} | ||||
|     }; | ||||
| 
 | ||||
|     std::vector<float> calculate_heights(const BoundingBoxf3& bb, float elevation, float initial_layer_height, float layer_height) const; | ||||
| 
 | ||||
|     // One level may contain multiple slices from multiple objects and their
 | ||||
|     // supports
 | ||||
|     using LayerRefs = std::vector<LayerRef>; | ||||
|  |  | |||
|  | @ -4564,11 +4564,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re | |||
| 
 | ||||
|                 const Print *print = m_process->fff_print(); | ||||
|                 float depth = print->get_wipe_tower_depth(); | ||||
| 
 | ||||
|                 // Calculate wipe tower brim spacing.
 | ||||
|                 const DynamicPrintConfig &print_config  = wxGetApp().preset_bundle->prints.get_edited_preset().config; | ||||
|                 double layer_height                     = print_config.opt_float("layer_height"); | ||||
|                 double first_layer_height               = print_config.get_abs_value("first_layer_height", layer_height); | ||||
|                 float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); | ||||
| 
 | ||||
|                 if (!print->is_step_done(psWipeTower)) | ||||
|                     depth = (900.f/w) * (float)(extruders_count - 1) ; | ||||
|                 int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( | ||||
|                     1000, x, y, w, depth, (float)height, a, m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), | ||||
|                     print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); | ||||
|                     brim_spacing * 4.5f); | ||||
|                 if (volume_idx_wipe_tower_old != -1) | ||||
|                     map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new; | ||||
|             } | ||||
|  | @ -7852,10 +7859,17 @@ void GLCanvas3D::_load_shells_fff() | |||
|         unsigned int extruders_count = config.nozzle_diameter.size(); | ||||
|         if ((extruders_count > 1) && config.single_extruder_multi_material && config.wipe_tower && !config.complete_objects) { | ||||
|             float depth = print->get_wipe_tower_depth(); | ||||
| 
 | ||||
|             // Calculate wipe tower brim spacing.
 | ||||
|             const DynamicPrintConfig &print_config  = wxGetApp().preset_bundle->prints.get_edited_preset().config; | ||||
|             double layer_height                     = print_config.opt_float("layer_height"); | ||||
|             double first_layer_height               = print_config.get_abs_value("first_layer_height", layer_height); | ||||
|             float brim_spacing = print->config().nozzle_diameter.values[0] * 1.25f - first_layer_height * (1. - M_PI_4); | ||||
| 
 | ||||
|             if (!print->is_step_done(psWipeTower)) | ||||
|                 depth = (900.f/config.wipe_tower_width) * (float)(extruders_count - 1) ; | ||||
|             m_volumes.load_wipe_tower_preview(1000, config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, | ||||
|                                               m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), print->config().nozzle_diameter.values[0] * 1.25f * 4.5f); | ||||
|                                               m_use_VBOs && m_initialized, !print->is_step_done(psWipeTower), brim_spacing * 4.5f); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1873,8 +1873,8 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b | |||
|     float render_color[3]; | ||||
|     for (int i = 0; i < (int)m_editing_mode_cache.size(); ++i) | ||||
|     { | ||||
|         const sla::SupportPoint& support_point = m_editing_mode_cache[i].first; | ||||
|         const bool& point_selected = m_editing_mode_cache[i].second; | ||||
|         const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point; | ||||
|         const bool& point_selected = m_editing_mode_cache[i].selected; | ||||
| 
 | ||||
|         // First decide about the color of the point.
 | ||||
|         if (picking) { | ||||
|  | @ -1889,7 +1889,7 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b | |||
|                 render_color[2] = 1.0f; | ||||
|             } | ||||
|             else { // neigher hover nor picking
 | ||||
|                 bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].first.is_new_island; | ||||
|                 bool supports_new_island = m_lock_unique_islands && m_editing_mode_cache[i].support_point.is_new_island; | ||||
|                 if (m_editing_mode) { | ||||
|                     render_color[0] = point_selected ? 1.0f : (supports_new_island ? 0.3f : 0.7f); | ||||
|                     render_color[1] = point_selected ? 0.3f : (supports_new_island ? 0.3f : 0.7f); | ||||
|  | @ -1903,12 +1903,32 @@ void GLGizmoSlaSupports::render_points(const GLCanvas3D::Selection& selection, b | |||
|         float render_color_emissive[4] = { 0.5f * render_color[0], 0.5f * render_color[1], 0.5f * render_color[2], 1.f}; | ||||
|         ::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive); | ||||
| 
 | ||||
|         // Now render the sphere. Inverse matrix of the instance scaling is applied so that the
 | ||||
|         // sphere does not scale with the object.
 | ||||
|         // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
 | ||||
|         ::glPushMatrix(); | ||||
|         ::glTranslated(support_point.pos(0), support_point.pos(1), support_point.pos(2)); | ||||
|         ::glMultMatrixd(instance_scaling_matrix_inverse.data()); | ||||
|         ::gluSphere(m_quadric, m_editing_mode_cache[i].first.head_front_radius * RenderPointScale, 64, 36); | ||||
| 
 | ||||
|         // Matrices set, we can render the point mark now.
 | ||||
|         // If in editing mode, we'll also render a cone pointing to the sphere.
 | ||||
|         if (m_editing_mode) { | ||||
|             if (m_editing_mode_cache[i].normal == Vec3f::Zero()) | ||||
|                 update_cache_entry_normal(i); // in case the normal is not yet cached, find and cache it
 | ||||
| 
 | ||||
|             Eigen::Quaterniond q; | ||||
|             q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_mode_cache[i].normal.cast<double>()); | ||||
|             Eigen::AngleAxisd aa(q); | ||||
|             ::glRotated(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)); | ||||
| 
 | ||||
|             const float cone_radius = 0.25f; // mm
 | ||||
|             const float cone_height = 0.75f; | ||||
|             ::glPushMatrix(); | ||||
|             ::glTranslatef(0.f, 0.f, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale); | ||||
|             ::gluCylinder(m_quadric, 0.f, cone_radius, cone_height, 36, 1); | ||||
|             ::glTranslatef(0.f, 0.f, cone_height); | ||||
|             ::gluDisk(m_quadric, 0.0, cone_radius, 36, 1); | ||||
|             ::glPopMatrix(); | ||||
|         } | ||||
|         ::gluSphere(m_quadric, m_editing_mode_cache[i].support_point.head_front_radius * RenderPointScale, 64, 36); | ||||
|         ::glPopMatrix(); | ||||
|     } | ||||
| 
 | ||||
|  | @ -1957,7 +1977,7 @@ void GLGizmoSlaSupports::update_mesh() | |||
|     m_AABB.init(m_V, m_F); | ||||
| } | ||||
| 
 | ||||
| Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) | ||||
| std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) | ||||
| { | ||||
|     // if the gizmo doesn't have the V, F structures for igl, calculate them first:
 | ||||
|     if (m_V.size() == 0) | ||||
|  | @ -1992,9 +2012,16 @@ Vec3f GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos) | |||
|     if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hit)) | ||||
|         throw std::invalid_argument("unproject_on_mesh(): No intersection found."); | ||||
| 
 | ||||
|     int fid = hit.id; | ||||
|     Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); | ||||
|     return bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)); | ||||
|     int fid = hit.id;   // facet id
 | ||||
|     Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
 | ||||
|     Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0))); | ||||
|     Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0))); | ||||
| 
 | ||||
|     // Calculate and return both the point and the facet normal.
 | ||||
|     return std::make_pair( | ||||
|             bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)), | ||||
|             a.cross(b) | ||||
|         ); | ||||
| } | ||||
| 
 | ||||
| // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
 | ||||
|  | @ -2046,10 +2073,9 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous | |||
| 
 | ||||
|             // If there is some selection, don't add new point and deselect everything instead.
 | ||||
|             if (m_selection_empty) { | ||||
|                 Vec3f new_pos; | ||||
|                 try { | ||||
|                     new_pos = unproject_on_mesh(mouse_position); // this can throw - we don't want to create a new point in that case
 | ||||
|                     m_editing_mode_cache.emplace_back(std::make_pair(sla::SupportPoint(new_pos, m_new_point_head_diameter/2.f, false), false)); | ||||
|                     std::pair<Vec3f, Vec3f> pos_and_normal = unproject_on_mesh(mouse_position); // don't create anything if this throws
 | ||||
|                     m_editing_mode_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); | ||||
|                     m_unsaved_changes = true; | ||||
|                 } | ||||
|                 catch (...) {      // not clicked on object
 | ||||
|  | @ -2090,7 +2116,7 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous | |||
| 
 | ||||
|             // Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh:
 | ||||
|             for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) { | ||||
|                 const sla::SupportPoint &support_point = m_editing_mode_cache[i].first; | ||||
|                 const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point; | ||||
|                 Vec3f pos = instance_matrix.cast<float>() * support_point.pos; | ||||
|                 pos(2) += z_offset; | ||||
|                   GLdouble out_x, out_y, out_z; | ||||
|  | @ -2166,7 +2192,7 @@ bool GLGizmoSlaSupports::mouse_event(SLAGizmoEventType action, const Vec2d& mous | |||
| void GLGizmoSlaSupports::delete_selected_points(bool force) | ||||
| { | ||||
|     for (unsigned int idx=0; idx<m_editing_mode_cache.size(); ++idx) { | ||||
|         if (m_editing_mode_cache[idx].second && (!m_editing_mode_cache[idx].first.is_new_island || !m_lock_unique_islands || force)) { | ||||
|         if (m_editing_mode_cache[idx].selected && (!m_editing_mode_cache[idx].support_point.is_new_island || !m_lock_unique_islands || force)) { | ||||
|             m_editing_mode_cache.erase(m_editing_mode_cache.begin() + (idx--)); | ||||
|             m_unsaved_changes = true; | ||||
|         } | ||||
|  | @ -2181,14 +2207,15 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) | |||
| 
 | ||||
| void GLGizmoSlaSupports::on_update(const UpdateData& data, const GLCanvas3D::Selection& selection) | ||||
| { | ||||
|     if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].first.is_new_island || !m_lock_unique_islands)) { | ||||
|         Vec3f new_pos; | ||||
|     if (m_editing_mode && m_hover_id != -1 && data.mouse_pos && (!m_editing_mode_cache[m_hover_id].support_point.is_new_island || !m_lock_unique_islands)) { | ||||
|         std::pair<Vec3f, Vec3f> pos_and_normal; | ||||
|         try { | ||||
|             new_pos = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); | ||||
|             pos_and_normal = unproject_on_mesh(Vec2d((*data.mouse_pos)(0), (*data.mouse_pos)(1))); | ||||
|         } | ||||
|         catch (...) { return; } | ||||
|         m_editing_mode_cache[m_hover_id].first.pos = new_pos; | ||||
|         m_editing_mode_cache[m_hover_id].first.is_new_island = false; | ||||
|         m_editing_mode_cache[m_hover_id].support_point.pos = pos_and_normal.first; | ||||
|         m_editing_mode_cache[m_hover_id].support_point.is_new_island = false; | ||||
|         m_editing_mode_cache[m_hover_id].normal = pos_and_normal.second; | ||||
|         m_unsaved_changes = true; | ||||
|         // Do not update immediately, wait until the mouse is released.
 | ||||
|         // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | ||||
|  | @ -2227,15 +2254,15 @@ void GLGizmoSlaSupports::render_tooltip_texture() const { | |||
| #endif // not ENABLE_IMGUI
 | ||||
| 
 | ||||
| 
 | ||||
| std::vector<ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const | ||||
| std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vector<std::string>& keys) const | ||||
| { | ||||
|     std::vector<ConfigOption*> out; | ||||
|     std::vector<const ConfigOption*> out; | ||||
| 
 | ||||
|     if (!m_model_object) | ||||
|         return out; | ||||
| 
 | ||||
|     DynamicPrintConfig& object_cfg = m_model_object->config; | ||||
|     DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; | ||||
|     const DynamicPrintConfig& object_cfg = m_model_object->config; | ||||
|     const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; | ||||
|     std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr; | ||||
| 
 | ||||
|     for (const std::string& key : keys) { | ||||
|  | @ -2255,6 +2282,18 @@ std::vector<ConfigOption*> GLGizmoSlaSupports::get_config_options(const std::vec | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const | ||||
| { | ||||
|     int idx = 0; | ||||
|     Eigen::Matrix<float, 1, 3> pp = m_editing_mode_cache[i].support_point.pos; | ||||
|     Eigen::Matrix<float, 1, 3> cc; | ||||
|     m_AABB.squared_distance(m_V, m_F, pp, idx, cc); | ||||
|     Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0))); | ||||
|     Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0))); | ||||
|     m_editing_mode_cache[i].normal = a.cross(b); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #if ENABLE_IMGUI | ||||
| void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const GLCanvas3D::Selection& selection) | ||||
|  | @ -2268,7 +2307,7 @@ RENDER_AGAIN: | |||
|     m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); | ||||
| 
 | ||||
|     const float scaling = m_imgui->get_style_scaling(); | ||||
|     const ImVec2 window_size(285.f * scaling, 260.f * scaling); | ||||
|     const ImVec2 window_size(285.f * scaling, 300.f * scaling); | ||||
|     ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); | ||||
|     ImGui::SetNextWindowSize(ImVec2(window_size)); | ||||
| 
 | ||||
|  | @ -2287,30 +2326,19 @@ RENDER_AGAIN: | |||
|         m_imgui->text(_(L("Shift + Left (+ drag) - select point(s)"))); | ||||
|         m_imgui->text(" ");  // vertical gap
 | ||||
| 
 | ||||
|         static const std::vector<float> options = {0.2f, 0.4f, 0.6f, 0.8f, 1.0f}; | ||||
|         static const std::vector<std::string> options_str = {"0.2", "0.4", "0.6", "0.8", "1.0"}; | ||||
|         int selection = -1; | ||||
|         for (size_t i = 0; i < options.size(); i++) { | ||||
|             if (options[i] == m_new_point_head_diameter) { selection = (int)i; } | ||||
|         } | ||||
|         float diameter_upper_cap = static_cast<ConfigOptionFloat*>(wxGetApp().preset_bundle->sla_prints.get_edited_preset().config.option("support_pillar_diameter"))->value; | ||||
|         if (m_new_point_head_diameter > diameter_upper_cap) | ||||
|             m_new_point_head_diameter = diameter_upper_cap; | ||||
| 
 | ||||
|         bool old_combo_state = m_combo_box_open; | ||||
|         // The combo is commented out for now, until the feature is supported by backend.
 | ||||
|         // m_combo_box_open = m_imgui->combo(_(L("Head diameter")), options_str, selection);
 | ||||
|         force_refresh |= (old_combo_state != m_combo_box_open); | ||||
| 
 | ||||
|         // float current_number = atof(str);
 | ||||
|         const float current_number = selection < options.size() ? options[selection] : m_new_point_head_diameter; | ||||
|         if (old_combo_state && !m_combo_box_open) // closing the combo must always change the sizes (even if the selection did not change)
 | ||||
|             for (auto& point_and_selection : m_editing_mode_cache) | ||||
|                 if (point_and_selection.second) { | ||||
|                     point_and_selection.first.head_front_radius = current_number / 2.f; | ||||
|         m_imgui->text(_(L("Head diameter: "))); | ||||
|         ImGui::SameLine(); | ||||
|         if (ImGui::SliderFloat("", &m_new_point_head_diameter, 0.1f, diameter_upper_cap, "%.1f")) { | ||||
|             // value was changed
 | ||||
|             for (auto& cache_entry : m_editing_mode_cache) | ||||
|                 if (cache_entry.selected) { | ||||
|                     cache_entry.support_point.head_front_radius = m_new_point_head_diameter / 2.f; | ||||
|                     m_unsaved_changes = true; | ||||
|                 } | ||||
| 
 | ||||
|         if (std::abs(current_number - m_new_point_head_diameter) > 0.001) { | ||||
|             force_refresh = true; | ||||
|             m_new_point_head_diameter = current_number; | ||||
|         } | ||||
| 
 | ||||
|         bool changed = m_lock_unique_islands; | ||||
|  | @ -2343,9 +2371,9 @@ RENDER_AGAIN: | |||
|         m_imgui->text(_(L("Minimal points distance: "))); | ||||
|         ImGui::SameLine(); | ||||
| 
 | ||||
|         std::vector<ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); | ||||
|         float density = static_cast<ConfigOptionInt*>(opts[0])->value; | ||||
|         float minimal_point_distance = static_cast<ConfigOptionFloat*>(opts[1])->value; | ||||
|         std::vector<const ConfigOption*> opts = get_config_options({"support_points_density_relative", "support_points_minimal_distance"}); | ||||
|         float density = static_cast<const ConfigOptionInt*>(opts[0])->value; | ||||
|         float minimal_point_distance = static_cast<const ConfigOptionFloat*>(opts[1])->value; | ||||
| 
 | ||||
|         bool value_changed = ImGui::SliderFloat("", &minimal_point_distance, 0.f, 20.f, "%.f mm"); | ||||
|         if (value_changed) | ||||
|  | @ -2451,6 +2479,10 @@ void GLGizmoSlaSupports::on_set_state() | |||
|         m_parent.toggle_model_objects_visibility(false); | ||||
|         if (m_model_object) | ||||
|             m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); | ||||
| 
 | ||||
|         // Set default head diameter from config.
 | ||||
|         const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; | ||||
|         m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value; | ||||
|     } | ||||
|     if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
 | ||||
|         if (m_model_object) { | ||||
|  | @ -2487,12 +2519,16 @@ void GLGizmoSlaSupports::select_point(int i) | |||
| { | ||||
|     if (i == AllPoints || i == NoPoints) { | ||||
|         for (auto& point_and_selection : m_editing_mode_cache) | ||||
|             point_and_selection.second = ( i == AllPoints ); | ||||
|             point_and_selection.selected = ( i == AllPoints ); | ||||
|         m_selection_empty = (i == NoPoints); | ||||
| 
 | ||||
|         if (i == AllPoints) | ||||
|             m_new_point_head_diameter = m_editing_mode_cache[0].support_point.head_front_radius * 2.f; | ||||
|     } | ||||
|     else { | ||||
|         m_editing_mode_cache[i].second = true; | ||||
|         m_editing_mode_cache[i].selected = true; | ||||
|         m_selection_empty = false; | ||||
|         m_new_point_head_diameter = m_editing_mode_cache[i].support_point.head_front_radius * 2.f; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -2502,7 +2538,7 @@ void GLGizmoSlaSupports::editing_mode_discard_changes() | |||
| { | ||||
|     m_editing_mode_cache.clear(); | ||||
|     for (const sla::SupportPoint& point : m_model_object->sla_support_points) | ||||
|         m_editing_mode_cache.push_back(std::make_pair(point, false)); | ||||
|         m_editing_mode_cache.emplace_back(point, false); | ||||
|     m_editing_mode = false; | ||||
|     m_unsaved_changes = false; | ||||
| } | ||||
|  | @ -2516,8 +2552,8 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() | |||
|     if (m_unsaved_changes) { | ||||
|         m_model_object->sla_points_status = sla::PointsStatus::UserModified; | ||||
|         m_model_object->sla_support_points.clear(); | ||||
|         for (const std::pair<sla::SupportPoint, bool>& point_and_selection : m_editing_mode_cache) | ||||
|             m_model_object->sla_support_points.push_back(point_and_selection.first); | ||||
|         for (const CacheEntry& cache_entry : m_editing_mode_cache) | ||||
|             m_model_object->sla_support_points.push_back(cache_entry.support_point); | ||||
| 
 | ||||
|         // Recalculate support structures once the editing mode is left.
 | ||||
|         // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
 | ||||
|  | @ -2534,7 +2570,7 @@ void GLGizmoSlaSupports::editing_mode_reload_cache() | |||
| { | ||||
|     m_editing_mode_cache.clear(); | ||||
|     for (const sla::SupportPoint& point : m_model_object->sla_support_points) | ||||
|         m_editing_mode_cache.push_back(std::make_pair(point, false)); | ||||
|         m_editing_mode_cache.emplace_back(point, false); | ||||
| 
 | ||||
|     m_unsaved_changes = false; | ||||
| } | ||||
|  |  | |||
|  | @ -471,7 +471,7 @@ private: | |||
|     ModelObject* m_old_model_object = nullptr; | ||||
|     int m_active_instance = -1; | ||||
|     int m_old_instance_id = -1; | ||||
|     Vec3f unproject_on_mesh(const Vec2d& mouse_pos); | ||||
|     std::pair<Vec3f, Vec3f> unproject_on_mesh(const Vec2d& mouse_pos); | ||||
| 
 | ||||
|     const float RenderPointScale = 1.f; | ||||
| 
 | ||||
|  | @ -484,6 +484,16 @@ private: | |||
|         Geometry::Transformation transformation; | ||||
|     }; | ||||
| 
 | ||||
|     class CacheEntry { | ||||
|     public: | ||||
|         CacheEntry(const sla::SupportPoint& point, bool sel, const Vec3f& norm = Vec3f::Zero()) : | ||||
|             support_point(point), selected(sel), normal(norm) {} | ||||
| 
 | ||||
|         sla::SupportPoint support_point; | ||||
|         bool selected; // whether the point is selected
 | ||||
|         Vec3f normal; | ||||
|     }; | ||||
| 
 | ||||
|     // This holds information to decide whether recalculation is necessary:
 | ||||
|     SourceDataSummary m_source_data; | ||||
| 
 | ||||
|  | @ -510,6 +520,7 @@ private: | |||
|     void render_points(const GLCanvas3D::Selection& selection, bool picking = false) const; | ||||
|     bool is_mesh_update_necessary() const; | ||||
|     void update_mesh(); | ||||
|     void update_cache_entry_normal(unsigned int i) const; | ||||
| 
 | ||||
| #if !ENABLE_IMGUI | ||||
|     void render_tooltip_texture() const; | ||||
|  | @ -520,10 +531,10 @@ private: | |||
|     bool m_lock_unique_islands = false; | ||||
|     bool m_editing_mode = false;            // Is editing mode active?
 | ||||
|     bool m_old_editing_state = false;       // To keep track of whether the user toggled between the modes (needed for imgui refreshes).
 | ||||
|     float m_new_point_head_diameter = 0.4f; // Size of a new point.
 | ||||
|     float m_new_point_head_diameter;        // Size of a new point.
 | ||||
|     float m_minimal_point_distance = 20.f; | ||||
|     float m_density = 100.f; | ||||
|     std::vector<std::pair<sla::SupportPoint, bool>> m_editing_mode_cache; // a support point and whether it is currently selected
 | ||||
|     mutable std::vector<CacheEntry> m_editing_mode_cache; // a support point and whether it is currently selected
 | ||||
| 
 | ||||
|     bool m_selection_rectangle_active = false; | ||||
|     Vec2d m_selection_rectangle_start_corner; | ||||
|  | @ -536,7 +547,7 @@ private: | |||
|     int m_canvas_width; | ||||
|     int m_canvas_height; | ||||
| 
 | ||||
|     std::vector<ConfigOption*> get_config_options(const std::vector<std::string>& keys) const; | ||||
|     std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const; | ||||
| 
 | ||||
|     // Methods that do the model_object and editing cache synchronization,
 | ||||
|     // editing mode selection, etc:
 | ||||
|  |  | |||
|  | @ -167,17 +167,6 @@ bool GUI_App::OnInit() | |||
|         if (app_config->dirty() && app_config->get("autosave") == "1") | ||||
|             app_config->save(); | ||||
| 
 | ||||
|         // ! Temporary workaround for the correct behavior of the Scrolled sidebar panel 
 | ||||
|         // Do this "manipulations" only once ( after (re)create of the application )
 | ||||
|         if (sidebar().obj_list()->GetMinHeight() > 15 * wxGetApp().em_unit()) | ||||
|         { | ||||
|             wxWindowUpdateLocker noUpdates_sidebar(&sidebar()); | ||||
|             sidebar().obj_list()->SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); | ||||
| 
 | ||||
|             // !!! to correct later layouts
 | ||||
|             update_mode(); // update view mode after fix of the object_list size
 | ||||
|         } | ||||
| 
 | ||||
|         this->obj_manipul()->update_if_dirty(); | ||||
| 
 | ||||
|         // Preset updating & Configwizard are done after the above initializations,
 | ||||
|  | @ -205,12 +194,13 @@ bool GUI_App::OnInit() | |||
|                 } | ||||
|                 preset_updater->sync(preset_bundle); | ||||
|             }); | ||||
| 
 | ||||
|             load_current_presets(); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     load_current_presets(); | ||||
| 
 | ||||
|     mainframe->Show(true); | ||||
|     update_mode(); // update view mode after fix of the object_list size
 | ||||
|     m_initialized = true; | ||||
|     return true; | ||||
| } | ||||
|  |  | |||
|  | @ -115,10 +115,7 @@ ObjectList::~ObjectList() | |||
| 
 | ||||
| void ObjectList::create_objects_ctrl() | ||||
| { | ||||
|     // temporary workaround for the correct behavior of the Scrolled sidebar panel:
 | ||||
|     // 1. set a height of the list to some big value 
 | ||||
|     // 2. change it to the normal min value (200) after first whole App updating/layouting
 | ||||
|     SetMinSize(wxSize(-1, 3000));   // #ys_FIXME 
 | ||||
|     SetMinSize(wxSize(-1, 15 * wxGetApp().em_unit())); | ||||
| 
 | ||||
|     m_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     m_sizer->Add(this, 1, wxGROW); | ||||
|  | @ -430,10 +427,8 @@ void ObjectList::OnContextMenu(wxDataViewEvent&) | |||
|     else if (title == _("Name") && pt.x >15 && | ||||
|              m_objects_model->GetBitmap(item).GetRefData() == m_bmp_manifold_warning.GetRefData()) | ||||
|     { | ||||
|         if (is_windows10()) { | ||||
|             const auto obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); | ||||
|             wxGetApp().plater()->fix_through_netfabb(obj_idx); | ||||
|         } | ||||
|         if (is_windows10()) | ||||
|             fix_through_netfabb(); | ||||
|     } | ||||
| #ifndef __WXMSW__ | ||||
|     GetMainWindow()->SetToolTip(""); // hide tooltip
 | ||||
|  | @ -1522,12 +1517,7 @@ bool ObjectList::is_splittable() | |||
|     if (!get_volume_by_item(item, volume) || !volume) | ||||
|         return false; | ||||
| 
 | ||||
| 	int splittable = volume->is_splittable(); | ||||
| 	if (splittable == -1) { | ||||
| 		splittable = (int)volume->mesh.has_multiple_patches(); | ||||
| 		volume->set_splittable(splittable); | ||||
| 	} | ||||
|     return splittable != 0; | ||||
|     return volume->is_splittable(); | ||||
| } | ||||
| 
 | ||||
| bool ObjectList::selected_instances_of_same_object() | ||||
|  | @ -2280,13 +2270,37 @@ void ObjectList::fix_through_netfabb() const | |||
|     if (!item) | ||||
|         return; | ||||
|      | ||||
|     ItemType type = m_objects_model->GetItemType(item); | ||||
|     const ItemType type = m_objects_model->GetItemType(item); | ||||
| 
 | ||||
|     const int obj_idx = type & itObject ? m_objects_model->GetIdByItem(item) : | ||||
|                         type & itVolume ? m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)) : -1; | ||||
| 
 | ||||
|     const int vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; | ||||
| 
 | ||||
|     wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx); | ||||
|      | ||||
|     if (type & itObject) | ||||
|         wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(item)); | ||||
|     else if (type & itVolume)  | ||||
|         wxGetApp().plater()->fix_through_netfabb(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)), | ||||
|                                                  m_objects_model->GetVolumeIdByItem(item));     | ||||
|     update_item_error_icon(obj_idx, vol_idx); | ||||
| } | ||||
| 
 | ||||
| void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const  | ||||
| { | ||||
|     const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) : | ||||
|                                 m_objects_model->GetItemByVolumeId(obj_idx, vol_idx); | ||||
|     if (!item) | ||||
|         return; | ||||
| 
 | ||||
|     auto model_object = (*m_objects)[obj_idx]; | ||||
| 
 | ||||
|     const stl_stats& stats = model_object->volumes[vol_idx<0 ? 0 : vol_idx]->mesh.stl.stats; | ||||
|     const int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + | ||||
|                        stats.facets_added + stats.facets_reversed + stats.backwards_edges; | ||||
| 
 | ||||
|     if (errors == 0) { | ||||
|         // delete Error_icon if all errors are fixed
 | ||||
|         wxVariant variant; | ||||
|         variant << PrusaDataViewBitmapText(from_u8(model_object->name), wxNullBitmap); | ||||
|         m_objects_model->SetValue(variant, item, 0); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void ObjectList::ItemValueChanged(wxDataViewEvent &event) | ||||
|  |  | |||
|  | @ -270,6 +270,7 @@ public: | |||
|     void split_instances(); | ||||
|     void rename_item(); | ||||
|     void fix_through_netfabb() const; | ||||
|     void update_item_error_icon(const int obj_idx, int vol_idx) const ; | ||||
| private: | ||||
|     void OnChar(wxKeyEvent& event); | ||||
|     void OnContextMenu(wxDataViewEvent &event); | ||||
|  |  | |||
|  | @ -33,9 +33,6 @@ namespace GUI { | |||
| #if !ENABLE_IMGUI | ||||
|     , m_gizmo_widget(nullptr) | ||||
| #endif // !ENABLE_IMGUI
 | ||||
|     , m_model(nullptr) | ||||
|     , m_config(nullptr) | ||||
|     , m_process(nullptr) | ||||
| { | ||||
|     init(parent, bed, camera, view_toolbar, model, config, process); | ||||
| } | ||||
|  | @ -202,18 +199,15 @@ Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_t | |||
|     , m_enabled(false) | ||||
|     , m_schedule_background_process(schedule_background_process_func) | ||||
| { | ||||
|     if (init(parent, bed, camera, view_toolbar, config, process, gcode_preview_data)) | ||||
|     if (init(parent, bed, camera, view_toolbar)) | ||||
|     { | ||||
|         show_hide_ui_elements("none"); | ||||
|         load_print(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data) | ||||
| bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) | ||||
| { | ||||
|     if ((config == nullptr) || (process == nullptr) || (gcode_preview_data == nullptr)) | ||||
|         return false; | ||||
| 
 | ||||
|     if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) | ||||
|         return false; | ||||
| 
 | ||||
|  | @ -222,7 +216,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view | |||
|     m_canvas = _3DScene::get_canvas(this->m_canvas_widget); | ||||
|     m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); | ||||
|     m_canvas->set_config(m_config); | ||||
|     m_canvas->set_process(process); | ||||
|     m_canvas->set_process(m_process); | ||||
|     m_canvas->enable_legend_texture(true); | ||||
|     m_canvas->enable_dynamic_background(true); | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,10 +39,6 @@ class View3D : public wxPanel | |||
|     wxPanel* m_gizmo_widget; | ||||
| #endif // !ENABLE_IMGUI
 | ||||
| 
 | ||||
|     Model* m_model; | ||||
|     DynamicPrintConfig* m_config; | ||||
|     BackgroundSlicingProcess* m_process; | ||||
| 
 | ||||
| public: | ||||
|     View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process); | ||||
|     virtual ~View3D(); | ||||
|  | @ -124,7 +120,7 @@ public: | |||
|     void refresh_print(); | ||||
| 
 | ||||
| private: | ||||
|     bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data); | ||||
|     bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); | ||||
| 
 | ||||
|     void bind_event_handlers(); | ||||
|     void unbind_event_handlers(); | ||||
|  |  | |||
|  | @ -107,11 +107,11 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) | |||
| 
 | ||||
|     ImGuiIO& io = ImGui::GetIO(); | ||||
|     io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY()); | ||||
|     io.MouseDown[0] = evt.LeftDown(); | ||||
|     io.MouseDown[1] = evt.RightDown(); | ||||
|     io.MouseDown[2] = evt.MiddleDown(); | ||||
|     io.MouseDown[0] = evt.LeftIsDown(); | ||||
|     io.MouseDown[1] = evt.RightIsDown(); | ||||
|     io.MouseDown[2] = evt.MiddleIsDown(); | ||||
| 
 | ||||
|     unsigned buttons = (evt.LeftDown() ? 1 : 0) | (evt.RightDown() ? 2 : 0) | (evt.MiddleDown() ? 4 : 0); | ||||
|     unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); | ||||
|     m_mouse_buttons = buttons; | ||||
| 
 | ||||
|     new_frame(); | ||||
|  | @ -441,6 +441,10 @@ void ImGuiWrapper::init_style() | |||
|     set_color(ImGuiCol_Header, COL_ORANGE_DARK); | ||||
|     set_color(ImGuiCol_HeaderHovered, COL_ORANGE_LIGHT); | ||||
|     set_color(ImGuiCol_HeaderActive, COL_ORANGE_LIGHT); | ||||
| 
 | ||||
|     // Slider
 | ||||
|     set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK); | ||||
|     set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT); | ||||
| } | ||||
| 
 | ||||
| void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) | ||||
|  |  | |||
|  | @ -524,6 +524,7 @@ struct Sidebar::priv | |||
|     Plater *plater; | ||||
| 
 | ||||
|     wxScrolledWindow *scrolled; | ||||
|     wxPanel* presets_panel; // Used for MSW better layouts
 | ||||
| 
 | ||||
|     PrusaModeSizer  *mode_sizer; | ||||
|     wxFlexGridSizer *sizer_presets; | ||||
|  | @ -555,8 +556,6 @@ void Sidebar::priv::show_preset_comboboxes() | |||
| { | ||||
|     const bool showSLA = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA; | ||||
| 
 | ||||
|     wxWindowUpdateLocker noUpdates_scrolled(scrolled->GetParent()); | ||||
|      | ||||
|     for (size_t i = 0; i < 4; ++i) | ||||
|         sizer_presets->Show(i, !showSLA); | ||||
| 
 | ||||
|  | @ -580,6 +579,7 @@ Sidebar::Sidebar(Plater *parent) | |||
|     p->scrolled = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * wxGetApp().em_unit(), -1)); | ||||
|     p->scrolled->SetScrollbars(0, 20, 1, 2); | ||||
| 
 | ||||
| 
 | ||||
|     // Sizer in the scrolled area
 | ||||
|     auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); | ||||
|     p->scrolled->SetSizer(scrolled_sizer); | ||||
|  | @ -591,12 +591,25 @@ Sidebar::Sidebar(Plater *parent) | |||
|     p->sizer_presets = new wxFlexGridSizer(10, 1, 1, 2); | ||||
|     p->sizer_presets->AddGrowableCol(0, 1); | ||||
|     p->sizer_presets->SetFlexibleDirection(wxBOTH); | ||||
| 
 | ||||
|     bool is_msw = false; | ||||
| #ifdef __WINDOWS__ | ||||
|     p->scrolled->SetDoubleBuffered(true); | ||||
| 
 | ||||
|     p->presets_panel = new wxPanel(p->scrolled, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); | ||||
|     p->presets_panel->SetSizer(p->sizer_presets); | ||||
| 
 | ||||
|     is_msw = true; | ||||
| #else | ||||
|     p->presets_panel = p->scrolled; | ||||
| #endif //__WINDOWS__
 | ||||
| 
 | ||||
|     p->sizer_filaments = new wxBoxSizer(wxVERTICAL); | ||||
| 
 | ||||
|     auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) { | ||||
|         auto *text = new wxStaticText(p->scrolled, wxID_ANY, label+" :"); | ||||
|         auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :"); | ||||
|         text->SetFont(wxGetApp().small_font()); | ||||
|         *combo = new PresetComboBox(p->scrolled, preset_type); | ||||
|         *combo = new PresetComboBox(p->presets_panel, preset_type); | ||||
| 
 | ||||
|         auto *sizer_presets = this->p->sizer_presets; | ||||
|         auto *sizer_filaments = this->p->sizer_filaments; | ||||
|  | @ -645,9 +658,7 @@ Sidebar::Sidebar(Plater *parent) | |||
|     p->object_settings->Hide(); | ||||
|     p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, margin_5); | ||||
| 
 | ||||
|     wxBitmap arrow_up(GUI::from_u8(Slic3r::var("brick_go.png")), wxBITMAP_TYPE_PNG); | ||||
|     p->btn_send_gcode = new wxButton(this, wxID_ANY, _(L("Send to printer"))); | ||||
|     p->btn_send_gcode->SetBitmap(arrow_up); | ||||
|     p->btn_send_gcode->SetFont(wxGetApp().bold_font()); | ||||
|     p->btn_send_gcode->Hide(); | ||||
| 
 | ||||
|  | @ -657,7 +668,9 @@ Sidebar::Sidebar(Plater *parent) | |||
| 
 | ||||
|     // Sizer in the scrolled area
 | ||||
|     scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); | ||||
|     scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); | ||||
|     is_msw ? | ||||
|         scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) :   | ||||
|         scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); | ||||
|     scrolled_sizer->Add(p->sizer_params, 1, wxEXPAND | wxLEFT, margin_5); | ||||
|     scrolled_sizer->Add(p->object_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); | ||||
|     scrolled_sizer->Add(p->sliced_info, 0, wxEXPAND | wxTOP | wxLEFT, margin_5); | ||||
|  | @ -695,7 +708,7 @@ Sidebar::Sidebar(Plater *parent) | |||
| Sidebar::~Sidebar() {} | ||||
| 
 | ||||
| void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) { | ||||
|     *combo = new PresetComboBox(p->scrolled, Slic3r::Preset::TYPE_FILAMENT); | ||||
|     *combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT); | ||||
| //         # copy icons from first choice
 | ||||
| //         $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
 | ||||
| 
 | ||||
|  | @ -757,6 +770,8 @@ void Sidebar::update_presets(Preset::Type preset_type) | |||
| 
 | ||||
| 	case Preset::TYPE_PRINTER: | ||||
| 	{ | ||||
| //         wxWindowUpdateLocker noUpdates_scrolled(p->scrolled);
 | ||||
| 
 | ||||
| 		// Update the print choosers to only contain the compatible presets, update the dirty flags.
 | ||||
|         if (print_tech == ptFFF) | ||||
| 			preset_bundle.prints.update_platter_ui(p->combo_print); | ||||
|  | @ -817,6 +832,11 @@ wxScrolledWindow* Sidebar::scrolled_panel() | |||
|     return p->scrolled; | ||||
| } | ||||
| 
 | ||||
| wxPanel* Sidebar::presets_panel() | ||||
| { | ||||
|     return p->presets_panel; | ||||
| } | ||||
| 
 | ||||
| ConfigOptionsGroup* Sidebar::og_freq_chng_params(const bool is_fff) | ||||
| { | ||||
|     return p->frequently_changed_parameters->get_og(is_fff); | ||||
|  | @ -2390,7 +2410,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) | |||
|     //! instead of 
 | ||||
|     //!     combo->GetStringSelection().ToUTF8().data()); 
 | ||||
| 
 | ||||
|     std::string selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); | ||||
|     const std::string& selected_string = combo->GetString(combo->GetSelection()).ToUTF8().data(); | ||||
| 
 | ||||
|     if (preset_type == Preset::TYPE_FILAMENT) { | ||||
|         wxGetApp().preset_bundle->set_filament_preset(idx, selected_string); | ||||
|  | @ -2402,12 +2422,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) | |||
|         wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); | ||||
|     }  | ||||
|     else { | ||||
|         for (Tab* tab : wxGetApp().tabs_list) { | ||||
|             if (tab->type() == preset_type) { | ||||
|                 tab->select_preset(selected_string); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|         wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); | ||||
|         wxGetApp().get_tab(preset_type)->select_preset(selected_string); | ||||
|     } | ||||
| 
 | ||||
|     // update plater with new config
 | ||||
|  | @ -2463,14 +2479,17 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) | |||
|     this->statusbar()->reset_cancel_callback(); | ||||
|     this->statusbar()->stop_busy(); | ||||
|    | ||||
| 	bool canceled = evt.GetInt() < 0; | ||||
|     bool success  = evt.GetInt() > 0; | ||||
|     const bool canceled = evt.GetInt() < 0; | ||||
| 	const bool error = evt.GetInt() == 0; | ||||
|     const bool success  = evt.GetInt() > 0; | ||||
|     // Reset the "export G-code path" name, so that the automatic background processing will be enabled again.
 | ||||
|     this->background_process.reset_export(); | ||||
|     if (! success) { | ||||
| 
 | ||||
|     if (error) { | ||||
|         wxString message = evt.GetString(); | ||||
|         if (message.IsEmpty()) | ||||
|             message = _(L("Export failed")); | ||||
|         show_error(q, message); | ||||
|         this->statusbar()->set_status_text(message); | ||||
|     } | ||||
| 	if (canceled) | ||||
|  | @ -2890,6 +2909,7 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const | |||
|         sidebar->show_export(!is_ready_to_slice); | ||||
|         sidebar->show_send(send_gcode_shown && !is_ready_to_slice); | ||||
|     } | ||||
|     sidebar->Layout(); | ||||
| } | ||||
| 
 | ||||
| void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const | ||||
|  | @ -3307,8 +3327,10 @@ void Plater::on_extruders_change(int num_extruders) | |||
| { | ||||
|     auto& choices = sidebar().combos_filament(); | ||||
| 
 | ||||
|     if (num_extruders == choices.size()) | ||||
|         return; | ||||
| 
 | ||||
|     wxWindowUpdateLocker noUpdates_scrolled_panel(&sidebar()/*.scrolled_panel()*/); | ||||
| //     sidebar().scrolled_panel()->Freeze();
 | ||||
| 
 | ||||
|     int i = choices.size(); | ||||
|     while ( i < num_extruders ) | ||||
|  |  | |||
|  | @ -51,6 +51,8 @@ public: | |||
|     int  get_extruder_idx() const               { return extruder_idx; } | ||||
|     void check_selection(); | ||||
| 
 | ||||
|     std::string     selected_preset_name; | ||||
| 
 | ||||
| private: | ||||
|     typedef std::size_t Marker; | ||||
|     enum { LABEL_ITEM_MARKER = 0x4d }; | ||||
|  | @ -81,6 +83,7 @@ public: | |||
|     ObjectList*             obj_list(); | ||||
|     ObjectSettings*         obj_settings(); | ||||
|     wxScrolledWindow*       scrolled_panel(); | ||||
|     wxPanel*                presets_panel(); | ||||
| 
 | ||||
|     ConfigOptionsGroup*     og_freq_chng_params(const bool is_fff); | ||||
|     wxButton*               get_wiping_dialog_button(); | ||||
|  |  | |||
|  | @ -457,6 +457,7 @@ const std::vector<std::string>& Preset::sla_print_options() | |||
|             "support_base_height", | ||||
|             "support_critical_angle", | ||||
|             "support_max_bridge_length", | ||||
|             "support_max_pillar_link_distance", | ||||
|             "support_object_elevation", | ||||
|             "support_points_density_relative", | ||||
|             "support_points_minimal_distance", | ||||
|  | @ -878,8 +879,10 @@ size_t PresetCollection::update_compatible_internal(const Preset &active_printer | |||
| // Hide the 
 | ||||
| void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) | ||||
| { | ||||
|     if (ui == nullptr) | ||||
|     if (ui == nullptr ||  | ||||
|         ui->selected_preset_name == this->get_selected_preset().name) | ||||
|         return; | ||||
| 
 | ||||
|     // Otherwise fill in the list from scratch.
 | ||||
|     ui->Freeze(); | ||||
|     ui->Clear(); | ||||
|  | @ -948,6 +951,8 @@ void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) | |||
| 	ui->SetSelection(selected_preset_item); | ||||
| 	ui->SetToolTip(ui->GetString(selected_preset_item)); | ||||
| 	ui->Thaw(); | ||||
| 
 | ||||
|     ui->selected_preset_name = this->get_selected_preset().name; | ||||
| } | ||||
| 
 | ||||
| size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible) | ||||
|  |  | |||
|  | @ -1436,7 +1436,8 @@ bool PresetBundle::parse_color(const std::string &scolor, unsigned char *rgb_out | |||
| void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) | ||||
| { | ||||
|     if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA || | ||||
|         this->filament_presets.size() <= idx_extruder ) | ||||
|         this->filament_presets.size() <= idx_extruder || | ||||
|         ui->selected_preset_name == this->filaments.find_preset(this->filament_presets[idx_extruder])->name) | ||||
|         return; | ||||
| 
 | ||||
|     unsigned char rgb[3]; | ||||
|  | @ -1525,6 +1526,8 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::Pr | |||
| 	ui->SetSelection(selected_preset_item); | ||||
| 	ui->SetToolTip(ui->GetString(selected_preset_item)); | ||||
|     ui->Thaw(); | ||||
| 
 | ||||
|     ui->selected_preset_name = this->filaments.find_preset(this->filament_presets[idx_extruder])->name; | ||||
| } | ||||
| 
 | ||||
| void PresetBundle::set_default_suppressed(bool default_suppressed) | ||||
|  |  | |||
|  | @ -73,6 +73,10 @@ void Tab::set_type() | |||
| // sub new
 | ||||
| void Tab::create_preset_tab() | ||||
| { | ||||
| #ifdef __WINDOWS__ | ||||
|     SetDoubleBuffered(true); | ||||
| #endif //__WINDOWS__
 | ||||
| 
 | ||||
|     m_preset_bundle = wxGetApp().preset_bundle; | ||||
| 
 | ||||
| 	// Vertical sizer to hold the choice menu and the rest of the page.
 | ||||
|  | @ -751,6 +755,10 @@ void Tab::load_key_value(const std::string& opt_key, const boost::any& value, bo | |||
| 
 | ||||
| void Tab::on_value_change(const std::string& opt_key, const boost::any& value) | ||||
| { | ||||
| 	if (wxGetApp().plater() == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
|     const bool is_fff = supports_printer_technology(ptFFF); | ||||
|     ConfigOptionsGroup* og_freq_chng_params = wxGetApp().sidebar().og_freq_chng_params(is_fff); | ||||
|     if (opt_key == "fill_density" || opt_key == "pad_enable") | ||||
|  | @ -783,25 +791,6 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) | |||
|         wxGetApp().plater()->on_extruders_change(boost::any_cast<size_t>(value)); | ||||
| 
 | ||||
| 	update(); | ||||
| 
 | ||||
|     // #ys_FIXME_to_delete
 | ||||
|     // Post event to the Plater after updating of the all dirty options
 | ||||
|     // It helps to avoid needless schedule_background_processing
 | ||||
| //     if (update_completed()) 
 | ||||
| //     if (m_update_stack.empty())
 | ||||
| //     {
 | ||||
| // //         wxCommandEvent event(EVT_TAB_VALUE_CHANGED);
 | ||||
| // //         event.SetEventObject(this);
 | ||||
| // //         event.SetString(opt_key);
 | ||||
| // //         if (opt_key == "extruders_count")
 | ||||
| // //         {
 | ||||
| // //             const int val = boost::any_cast<size_t>(value);
 | ||||
| // //             event.SetInt(val);
 | ||||
| // //         }
 | ||||
| // // 
 | ||||
| // //         wxPostEvent(this, event);
 | ||||
| //         wxGetApp().mainframe->on_value_changed(m_config);
 | ||||
| //     }
 | ||||
| } | ||||
| 
 | ||||
| // Show/hide the 'purging volumes' button
 | ||||
|  | @ -824,9 +813,13 @@ void Tab::update_wiping_button_visibility() { | |||
| // To update the content of the selection boxes,
 | ||||
| // to update the filament colors of the selection boxes,
 | ||||
| // to update the "dirty" flags of the selection boxes,
 | ||||
| // to uddate number of "filament" selection boxes when the number of extruders change.
 | ||||
| // to update number of "filament" selection boxes when the number of extruders change.
 | ||||
| void Tab::on_presets_changed() | ||||
| { | ||||
|     // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
 | ||||
|     wxGetApp().plater()->sidebar().update_presets(m_type); | ||||
| 	update_preset_description_line(); | ||||
| 
 | ||||
|     // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors.
 | ||||
|     for (auto t: m_dependent_tabs) | ||||
|     { | ||||
|  | @ -837,16 +830,6 @@ void Tab::on_presets_changed() | |||
|     // clear m_dependent_tabs after first update from select_preset()
 | ||||
|     // to avoid needless preset loading from update() function
 | ||||
|     m_dependent_tabs.clear(); | ||||
| 
 | ||||
|     // #ys_FIXME_to_delete
 | ||||
| // 	wxCommandEvent event(EVT_TAB_PRESETS_CHANGED);
 | ||||
| // 	event.SetEventObject(this);
 | ||||
| // 	wxPostEvent(this, event);
 | ||||
| 
 | ||||
|     // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets
 | ||||
|     wxGetApp().plater()->sidebar().update_presets(m_type); | ||||
| 
 | ||||
| 	update_preset_description_line(); | ||||
| } | ||||
| 
 | ||||
| void Tab::update_preset_description_line() | ||||
|  | @ -2472,7 +2455,7 @@ void Tab::load_current_preset() | |||
| //Regerenerate content of the page tree.
 | ||||
| void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) | ||||
| { | ||||
| 	Freeze(); | ||||
| // 	Freeze();
 | ||||
| 
 | ||||
| 	// get label of the currently selected item
 | ||||
|     const auto sel_item = m_treectrl->GetSelection(); | ||||
|  | @ -2498,7 +2481,7 @@ void Tab::rebuild_page_tree(bool tree_sel_change_event /*= false*/) | |||
| 		// this is triggered on first load, so we don't disable the sel change event
 | ||||
| 		m_treectrl->SelectItem(m_treectrl->GetFirstVisibleItem());//! (treectrl->GetFirstChild(rootItem));
 | ||||
| 	} | ||||
| 	Thaw(); | ||||
| // 	Thaw();
 | ||||
| } | ||||
| 
 | ||||
| void Tab::update_page_tree_visibility() | ||||
|  | @ -3273,7 +3256,8 @@ void TabSLAPrint::build() | |||
|     optgroup->append_single_option_line("support_pillar_diameter"); | ||||
|     optgroup->append_single_option_line("support_pillar_connection_mode"); | ||||
|     optgroup->append_single_option_line("support_buildplate_only"); | ||||
|     optgroup->append_single_option_line("support_pillar_widening_factor"); | ||||
|     // TODO: This parameter is not used at the moment.
 | ||||
|     // 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_object_elevation"); | ||||
|  | @ -3281,6 +3265,7 @@ void TabSLAPrint::build() | |||
|     optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); | ||||
|     optgroup->append_single_option_line("support_critical_angle"); | ||||
|     optgroup->append_single_option_line("support_max_bridge_length"); | ||||
|     optgroup->append_single_option_line("support_max_pillar_link_distance"); | ||||
| 
 | ||||
|     optgroup = page->new_optgroup(_(L("Automatic generation"))); | ||||
|     optgroup->append_single_option_line("support_points_density_relative"); | ||||
|  | @ -3339,11 +3324,37 @@ void TabSLAPrint::update() | |||
|         return; // #ys_FIXME
 | ||||
| 
 | ||||
| // #ys_FIXME
 | ||||
| //     m_update_cnt++;
 | ||||
| //     ! something to update
 | ||||
| //     m_update_cnt--;
 | ||||
| // 
 | ||||
| //     if (m_update_cnt == 0)
 | ||||
|      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)); | ||||
|          } | ||||
| 
 | ||||
|          load_config(new_conf); | ||||
|      } | ||||
| 
 | ||||
|      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)); | ||||
|          } | ||||
| 
 | ||||
|          load_config(new_conf); | ||||
|      } | ||||
| 
 | ||||
|      m_update_cnt--; | ||||
| 
 | ||||
|      if (m_update_cnt == 0) | ||||
|     wxGetApp().mainframe->on_config_changed(m_config); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 bubnikv
						bubnikv